diff --git a/3rdparty/ext_qt/0051-Add-workaround-for-handling-table-press-correctly-in.patch b/3rdparty/ext_qt/0051-Add-workaround-for-handling-table-press-correctly-in.patch index 2dfecbda5c..beeb593cc2 100644 --- a/3rdparty/ext_qt/0051-Add-workaround-for-handling-table-press-correctly-in.patch +++ b/3rdparty/ext_qt/0051-Add-workaround-for-handling-table-press-correctly-in.patch @@ -1,243 +1,251 @@ -From 9a2a150476d51d786e011e1ddde4bb77c7b9d146 Mon Sep 17 00:00:00 2001 +From 5a518a022d840571e6fb6cb1448fc801a2678aba Mon Sep 17 00:00:00 2001 From: Dmitry Kazakov Date: Wed, 15 May 2019 19:54:52 +0300 -Subject: [PATCH 2/2] Add workaround for handling table press correctly in - WinInk mode +Subject: [PATCH] Add workaround for handling table press correctly in WinInk + mode Original problem: widgets do not get synthesized mouse-down and mouse-press events until the stylus is released Reason: if the app accepts the event, WndProc should report that to the system (by returning true). This is the only way to prevent Windows from starting some system-wide gestures, like click+hold -> right button click. If we ignore the event, then OS postpones all synthesized mouse events until the entire gesture is completed. The patch implements a "hackish" workaround for the original problem by using the following rules: 1) All tablet-move events are ignored (without synthesized mouse events OS doesn't generate any Enter/Leave events) 2) All not-accepted tablet press- and release-events and also reported as ignored (without it D&D doesn't work). 3) All accepted tablet press- and release-events are reported as "accepted", **but** we artificially synthesize mouse events for them. -TODO: there are still two problems: +TODO: there are still one problem: 1) Perhaps this synthesizeMouseEvent() is not needed at all. But we should first check if Qt relies on these synthesized messages anywhere in the code or not. -2) If we still keep synthesizeMouseEvent(), then it should fetch actual - tablet buttons from QGuiApplicationPrivate::tabletDevicePoint(). Right - now it always synthesizes left-click, whatever the button was pressed/ - released. - See bug: https://bugs.kde.org/show_bug.cgi?id=406668 --- src/gui/kernel/qguiapplication.cpp | 1 + src/gui/kernel/qwindowsysteminterface.cpp | 20 ++--- src/gui/kernel/qwindowsysteminterface.h | 8 +- - .../windows/qwindowspointerhandler.cpp | 75 ++++++++++++++++++- - 4 files changed, 86 insertions(+), 18 deletions(-) + .../windows/qwindowspointerhandler.cpp | 88 ++++++++++++++++++- + 4 files changed, 99 insertions(+), 18 deletions(-) diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp index 3541c1ae..681cbd85 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -2536,6 +2536,7 @@ void QGuiApplicationPrivate::processTabletEvent(QWindowSystemInterfacePrivate::T tabletEvent.setTimestamp(e->timestamp); QGuiApplication::sendSpontaneousEvent(window, &tabletEvent); pointData.state = e->buttons; + e->eventAccepted = tabletEvent.isAccepted(); if (!tabletEvent.isAccepted() && !QWindowSystemInterfacePrivate::TabletEvent::platformSynthesizesMouse && qApp->testAttribute(Qt::AA_SynthesizeMouseForUnhandledTabletEvents)) { diff --git a/src/gui/kernel/qwindowsysteminterface.cpp b/src/gui/kernel/qwindowsysteminterface.cpp index 0bedae1b..779a9ee4 100644 --- a/src/gui/kernel/qwindowsysteminterface.cpp +++ b/src/gui/kernel/qwindowsysteminterface.cpp @@ -869,7 +869,7 @@ void QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(boo platformSynthesizesMouse = v; } -void QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global, +bool QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers) @@ -880,36 +880,36 @@ void QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, QHighDpi::fromNativePixels(global, window), device, pointerType, buttons, pressure, xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers); - QWindowSystemInterfacePrivate::handleWindowSystemEvent(e); + return QWindowSystemInterfacePrivate::handleWindowSystemEvent(e); } -void QWindowSystemInterface::handleTabletEvent(QWindow *window, const QPointF &local, const QPointF &global, +bool QWindowSystemInterface::handleTabletEvent(QWindow *window, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers) { ulong time = QWindowSystemInterfacePrivate::eventTime.elapsed(); - handleTabletEvent(window, time, local, global, device, pointerType, buttons, pressure, + return handleTabletEvent(window, time, local, global, device, pointerType, buttons, pressure, xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers); } #if QT_DEPRECATED_SINCE(5, 10) -void QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, bool down, const QPointF &local, const QPointF &global, +bool QWindowSystemInterface::handleTabletEvent(QWindow *window, ulong timestamp, bool down, const QPointF &local, const QPointF &global, int device, int pointerType, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers) { - handleTabletEvent(window, timestamp, local, global, device, pointerType, (down ? Qt::LeftButton : Qt::NoButton), pressure, - xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers); + return handleTabletEvent(window, timestamp, local, global, device, pointerType, (down ? Qt::LeftButton : Qt::NoButton), pressure, + xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers); } -void QWindowSystemInterface::handleTabletEvent(QWindow *window, bool down, const QPointF &local, const QPointF &global, +bool QWindowSystemInterface::handleTabletEvent(QWindow *window, bool down, const QPointF &local, const QPointF &global, int device, int pointerType, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers) { - handleTabletEvent(window, local, global, device, pointerType, (down ? Qt::LeftButton : Qt::NoButton), pressure, - xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers); + return handleTabletEvent(window, local, global, device, pointerType, (down ? Qt::LeftButton : Qt::NoButton), pressure, + xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers); } #endif // QT_DEPRECATED_SINCE(5, 10) diff --git a/src/gui/kernel/qwindowsysteminterface.h b/src/gui/kernel/qwindowsysteminterface.h index 1dde9130..0ad89547 100644 --- a/src/gui/kernel/qwindowsysteminterface.h +++ b/src/gui/kernel/qwindowsysteminterface.h @@ -243,20 +243,20 @@ public: static void handleFileOpenEvent(const QString& fileName); static void handleFileOpenEvent(const QUrl &url); - static void handleTabletEvent(QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global, + static bool handleTabletEvent(QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers = Qt::NoModifier); - static void handleTabletEvent(QWindow *window, const QPointF &local, const QPointF &global, + static bool handleTabletEvent(QWindow *window, const QPointF &local, const QPointF &global, int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers = Qt::NoModifier); #if QT_DEPRECATED_SINCE(5, 10) - QT_DEPRECATED static void handleTabletEvent(QWindow *window, ulong timestamp, bool down, const QPointF &local, const QPointF &global, + QT_DEPRECATED static bool handleTabletEvent(QWindow *window, ulong timestamp, bool down, const QPointF &local, const QPointF &global, int device, int pointerType, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers = Qt::NoModifier); - QT_DEPRECATED static void handleTabletEvent(QWindow *window, bool down, const QPointF &local, const QPointF &global, + QT_DEPRECATED static bool handleTabletEvent(QWindow *window, bool down, const QPointF &local, const QPointF &global, int device, int pointerType, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers modifiers = Qt::NoModifier); diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp -index 07f4d41e..58a5d19f 100644 +index 07f4d41e..e8c98948 100644 --- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp +++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp @@ -518,6 +518,58 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd, return false; // Allow mouse messages to be generated. } +void synthesizeMouseEvent(QEvent::Type type, Qt::MouseButton button, const POINTER_PEN_INFO &penInfo) +{ + // Update the cursor position + BOOL result = SetCursorPos(penInfo.pointerInfo.ptPixelLocationRaw.x, penInfo.pointerInfo.ptPixelLocationRaw.y); + if (!result) { + qCDebug(lcQpaEvents).noquote().nospace() << showbase + << __FUNCTION__ << "SetCursorPos failed, err" << GetLastError(); + return; + } + // Send mousebutton down/up events. Windows stores the button state. + DWORD inputDataFlags = 0; + switch (type) { + case QEvent::TabletPress: + switch (button) { + case Qt::LeftButton: + inputDataFlags = MOUSEEVENTF_LEFTDOWN; + break; + case Qt::RightButton: + inputDataFlags = MOUSEEVENTF_RIGHTDOWN; + break; + default: + return; + } + break; + case QEvent::TabletRelease: + switch (button) { + case Qt::LeftButton: + inputDataFlags = MOUSEEVENTF_LEFTUP; + break; + case Qt::RightButton: + inputDataFlags = MOUSEEVENTF_RIGHTUP; + break; + default: + return; + } + break; + case QEvent::TabletMove: + default: + return; + } + INPUT inputData = {}; + inputData.type = INPUT_MOUSE; + inputData.mi.dwFlags = inputDataFlags; + inputData.mi.dwExtraInfo = 0xFF515700 | 0x01; // https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320%28v=vs.85%29.aspx + UINT result2 = SendInput(1, &inputData, sizeof(inputData)); + if (result2 != 1) { + qCDebug(lcQpaEvents).noquote().nospace() << showbase + << __FUNCTION__ << "SendInput failed, err" << GetLastError(); + return; + } +} + bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, PVOID vPenInfo) { -@@ -610,10 +662,25 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin +@@ -610,10 +662,38 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin } const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers(); - QWindowSystemInterface::handleTabletEvent(target, localPos, hiResGlobalPos, device, type, mouseButtons, - pressure, xTilt, yTilt, tangentialPressure, rotation, z, - sourceDevice, keyModifiers); - return false; // Allow mouse messages to be generated. ++ const Qt::MouseButtons oldButtons = QGuiApplicationPrivate::tabletDevicePoint(sourceDevice).state; ++ + const bool accepted = + QWindowSystemInterface::handleTabletEvent(target, localPos, hiResGlobalPos, device, type, mouseButtons, + pressure, xTilt, yTilt, tangentialPressure, rotation, z, + sourceDevice, keyModifiers); + -+ /** -+ * TODO: right now we can fake-synthesize only left button clicks. For -+ * synthesizing right button clicks we should fetch tablet state -+ * from QGuiApplicationPrivate::tabletDevicePoint() -+ */ -+ if (accepted && (msg.message == WM_POINTERDOWN || msg.message == WM_POINTERUP)) { ++ const Qt::MouseButtons changedButtons = ++ oldButtons ^ QGuiApplicationPrivate::tabletDevicePoint(sourceDevice).state; ++ ++ Qt::MouseButton pressedButton = Qt::NoButton; ++ ++ const QVector supportedButtons = ++ {Qt::LeftButton, Qt::RightButton, Qt::MiddleButton}; ++ ++ for (Qt::MouseButton button : supportedButtons) { ++ if (changedButtons & button) { ++ pressedButton = button; ++ break; ++ } ++ } ++ ++ if (accepted && pressedButton != Qt::NoButton && ++ (msg.message == WM_POINTERDOWN || msg.message == WM_POINTERUP)) { ++ + QEvent::Type type = (msg.message == WM_POINTERDOWN) ? QEvent::TabletPress : QEvent::TabletRelease; -+ Qt::MouseButton button = Qt::LeftButton; + -+ synthesizeMouseEvent(type, button, *penInfo); ++ synthesizeMouseEvent(type, pressedButton, *penInfo); + return true; + } else { + return false; // Allow mouse messages to be generated by OS + } } } return true; -- 2.20.1.windows.1 diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp index 5563ab15d7..8a847f665b 100644 --- a/libs/libkis/Document.cpp +++ b/libs/libkis/Document.cpp @@ -1,993 +1,994 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Document.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include "kis_animation_importer.h" #include #include #include #include struct Document::Private { Private() {} QPointer document; }; Document::Document(KisDocument *document, QObject *parent) : QObject(parent) , d(new Private) { d->document = document; } Document::~Document() { delete d; } bool Document::operator==(const Document &other) const { return (d->document == other.d->document); } bool Document::operator!=(const Document &other) const { return !(operator==(other)); } bool Document::batchmode() const { if (!d->document) return false; return d->document->fileBatchMode(); } void Document::setBatchmode(bool value) { if (!d->document) return; d->document->setFileBatchMode(value); } Node *Document::activeNode() const { QList activeNodes; Q_FOREACH(QPointer view, KisPart::instance()->views()) { if (view && view->document() == d->document) { activeNodes << view->currentNode(); } } if (activeNodes.size() > 0) { QList nodes = LibKisUtils::createNodeList(activeNodes, d->document->image()); return nodes.first(); } return 0; } void Document::setActiveNode(Node* value) { if (!value->node()) return; KisMainWindow *mainWin = KisPart::instance()->currentMainwindow(); if (!mainWin) return; KisViewManager *viewManager = mainWin->viewManager(); if (!viewManager) return; if (viewManager->document() != d->document) return; KisNodeManager *nodeManager = viewManager->nodeManager(); if (!nodeManager) return; KisNodeSelectionAdapter *selectionAdapter = nodeManager->nodeSelectionAdapter(); if (!selectionAdapter) return; selectionAdapter->setActiveNode(value->node()); } QList Document::topLevelNodes() const { if (!d->document) return QList(); Node n(d->document->image(), d->document->image()->rootLayer()); return n.childNodes(); } Node *Document::nodeByName(const QString &name) const { if (!d->document) return 0; KisNodeSP node = d->document->image()->rootLayer()->findChildByName(name); return new Node(d->document->image(), node); } QString Document::colorDepth() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorDepthId().id(); } QString Document::colorModel() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorModelId().id(); } QString Document::colorProfile() const { if (!d->document) return ""; return d->document->image()->colorSpace()->profile()->name(); } bool Document::setColorProfile(const QString &value) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(value); if (!profile) return false; bool retval = d->document->image()->assignImageProfile(profile); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return retval; } bool Document::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); if (!colorSpace) return false; d->document->image()->convertImageColorSpace(colorSpace, KoColorConversionTransformation::IntentPerceptual, KoColorConversionTransformation::HighQuality | KoColorConversionTransformation::NoOptimization); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return true; } QColor Document::backgroundColor() { if (!d->document) return QColor(); if (!d->document->image()) return QColor(); const KoColor color = d->document->image()->defaultProjectionColor(); return color.toQColor(); } bool Document::setBackgroundColor(const QColor &color) { if (!d->document) return false; if (!d->document->image()) return false; KoColor background = KoColor(color, d->document->image()->colorSpace()); d->document->image()->setDefaultProjectionColor(background); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return true; } QString Document::documentInfo() const { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = d->document->documentInfo()->save(doc); return doc.toString(); } void Document::setDocumentInfo(const QString &document) { KoXmlDocument doc; QString errorMsg; int errorLine, errorColumn; doc.setContent(document, &errorMsg, &errorLine, &errorColumn); d->document->documentInfo()->load(doc); } QString Document::fileName() const { if (!d->document) return QString(); return d->document->url().toLocalFile(); } void Document::setFileName(QString value) { if (!d->document) return; QString mimeType = KisMimeDatabase::mimeTypeForFile(value, false); d->document->setMimeType(mimeType.toLatin1()); d->document->setUrl(QUrl::fromLocalFile(value)); } int Document::height() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->height(); } void Document::setHeight(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), d->document->image()->bounds().y(), d->document->image()->width(), value); } QString Document::name() const { if (!d->document) return ""; return d->document->documentInfo()->aboutInfo("title"); } void Document::setName(QString value) { if (!d->document) return; d->document->documentInfo()->setAboutInfo("title", value); } int Document::resolution() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return qRound(d->document->image()->xRes() * 72); } void Document::setResolution(int value) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; d->document->image()->setResolution(value / 72.0, value / 72.0); } Node *Document::rootNode() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return new Node(image, image->root()); } Selection *Document::selection() const { if (!d->document) return 0; if (!d->document->image()) return 0; if (!d->document->image()->globalSelection()) return 0; return new Selection(d->document->image()->globalSelection()); } void Document::setSelection(Selection* value) { if (!d->document) return; if (!d->document->image()) return; if (value) { d->document->image()->setGlobalSelection(value->selection()); } else { d->document->image()->setGlobalSelection(0); } } int Document::width() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->width(); } void Document::setWidth(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), d->document->image()->bounds().y(), value, d->document->image()->height()); } int Document::xOffset() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->bounds().x(); } void Document::setXOffset(int x) { if (!d->document) return; if (!d->document->image()) return; resizeImage(x, d->document->image()->bounds().y(), d->document->image()->width(), d->document->image()->height()); } int Document::yOffset() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->bounds().y(); } void Document::setYOffset(int y) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), y, d->document->image()->width(), d->document->image()->height()); } double Document::xRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->xRes()*72.0; } void Document::setXRes(double xRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(xRes/72.0, d->document->image()->yRes()); } double Document::yRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->yRes()*72.0; } void Document::setYRes(double yRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(d->document->image()->xRes(), yRes/72.0); } QByteArray Document::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->document) return ba; KisImageSP image = d->document->image(); if (!image) return ba; KisPaintDeviceSP dev = image->projection(); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } bool Document::close() { bool retval = d->document->closeUrl(false); Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == d->document) { view->close(); view->closeView(); view->deleteLater(); } } KisPart::instance()->removeDocument(d->document); d->document = 0; return retval; } void Document::crop(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc(x, y, w, h); image->cropImage(rc); } bool Document::exportImage(const QString &filename, const InfoObject &exportConfiguration) { if (!d->document) return false; const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename, false); const QByteArray outputFormat = outputFormatString.toLatin1(); return d->document->exportDocumentSync(QUrl::fromLocalFile(filename), outputFormat, exportConfiguration.configuration()); } void Document::flatten() { if (!d->document) return; if (!d->document->image()) return; d->document->image()->flatten(0); } void Document::resizeImage(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc; rc.setX(x); rc.setY(y); rc.setWidth(w); rc.setHeight(h); image->resizeImage(rc); } void Document::scaleImage(int w, int h, int xres, int yres, QString strategy) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc = image->bounds(); rc.setWidth(w); rc.setHeight(h); KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy); if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); image->scaleImage(rc.size(), xres/72, yres/72, actualStrategy); } void Document::rotateImage(double radians) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; image->rotateImage(radians); } void Document::shearImage(double angleX, double angleY) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; image->shear(angleX, angleY); } bool Document::save() { if (!d->document) return false; if (d->document->url().isEmpty()) return false; bool retval = d->document->save(true, 0); d->document->waitForSavingToComplete(); return retval; } bool Document::saveAs(const QString &filename) { if (!d->document) return false; const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename, false); const QByteArray outputFormat = outputFormatString.toLatin1(); QUrl oldUrl = d->document->url(); d->document->setUrl(QUrl::fromLocalFile(filename)); bool retval = d->document->saveAs(QUrl::fromLocalFile(filename), outputFormat, true); d->document->waitForSavingToComplete(); d->document->setUrl(oldUrl); return retval; } Node* Document::createNode(const QString &name, const QString &nodeType) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); Node *node = 0; if (nodeType.toLower()== "paintlayer") { node = new Node(image, new KisPaintLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "grouplayer") { node = new Node(image, new KisGroupLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "filelayer") { node = new Node(image, new KisFileLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "filterlayer") { node = new Node(image, new KisAdjustmentLayer(image, name, 0, 0)); } else if (nodeType.toLower() == "filllayer") { node = new Node(image, new KisGeneratorLayer(image, name, 0, 0)); } else if (nodeType.toLower() == "clonelayer") { node = new Node(image, new KisCloneLayer(0, image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "vectorlayer") { node = new Node(image, new KisShapeLayer(d->document->shapeController(), image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "transparencymask") { node = new Node(image, new KisTransparencyMask()); } else if (nodeType.toLower() == "filtermask") { node = new Node(image, new KisFilterMask()); } else if (nodeType.toLower() == "transformmask") { node = new Node(image, new KisTransformMask()); } else if (nodeType.toLower() == "selectionmask") { node = new Node(image, new KisSelectionMask(image)); } return node; } GroupLayer *Document::createGroupLayer(const QString &name) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new GroupLayer(image, name); } FileLayer *Document::createFileLayer(const QString &name, const QString fileName, const QString scalingMethod) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new FileLayer(image, name, this->fileName(), fileName, scalingMethod); } FilterLayer *Document::createFilterLayer(const QString &name, Filter &filter, Selection &selection) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new FilterLayer(image, name, filter, selection); } FillLayer *Document::createFillLayer(const QString &name, const QString generatorName, InfoObject &configuration, Selection &selection) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorName); if (generator) { KisFilterConfigurationSP config = generator->defaultConfiguration(); Q_FOREACH(const QString property, configuration.properties().keys()) { config->setProperty(property, configuration.property(property)); } return new FillLayer(image, name, config, selection); } return 0; } CloneLayer *Document::createCloneLayer(const QString &name, const Node *source) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); KisLayerSP layer = qobject_cast(source->node().data()); return new CloneLayer(image, name, layer); } VectorLayer *Document::createVectorLayer(const QString &name) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new VectorLayer(d->document->shapeController(), image, name); } FilterMask *Document::createFilterMask(const QString &name, Filter &filter, const Node *selection_source) { if (!d->document) return 0; if (!d->document->image()) return 0; if(!selection_source) return 0; KisLayerSP layer = qobject_cast(selection_source->node().data()); if(layer.isNull()) return 0; KisImageSP image = d->document->image(); FilterMask* mask = new FilterMask(image, name, filter); qobject_cast(mask->node().data())->initSelection(layer); return mask; } FilterMask *Document::createFilterMask(const QString &name, Filter &filter, Selection &selection) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); FilterMask* mask = new FilterMask(image, name, filter); qobject_cast(mask->node().data())->setSelection(selection.selection()); return mask; } SelectionMask *Document::createSelectionMask(const QString &name) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new SelectionMask(image, name); } QImage Document::projection(int x, int y, int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->image()->convertToQImage(x, y, w, h, 0); } QImage Document::thumbnail(int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->generatePreview(QSize(w, h)).toImage(); } void Document::lock() { if (!d->document || !d->document->image()) return; d->document->image()->barrierLock(); } void Document::unlock() { if (!d->document || !d->document->image()) return; d->document->image()->unlock(); } void Document::waitForDone() { if (!d->document || !d->document->image()) return; d->document->image()->waitForDone(); } bool Document::tryBarrierLock() { if (!d->document || !d->document->image()) return false; return d->document->image()->tryBarrierLock(); } bool Document::isIdle() { if (!d->document || !d->document->image()) return false; return d->document->image()->isIdle(); } void Document::refreshProjection() { if (!d->document || !d->document->image()) return; d->document->image()->refreshGraph(); } QList Document::horizontalGuides() const { QList lines; if (!d->document || !d->document->image()) return lines; KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform().inverted(); QList untransformedLines = d->document->guidesConfig().horizontalGuideLines(); for (int i = 0; i< untransformedLines.size(); i++) { qreal line = untransformedLines[i]; lines.append(transform.map(QPointF(line, line)).x()); } return lines; } QList Document::verticalGuides() const { QList lines; if (!d->document || !d->document->image()) return lines; KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform().inverted(); QList untransformedLines = d->document->guidesConfig().verticalGuideLines(); for (int i = 0; i< untransformedLines.size(); i++) { qreal line = untransformedLines[i]; lines.append(transform.map(QPointF(line, line)).y()); } return lines; } bool Document::guidesVisible() const { return d->document->guidesConfig().lockGuides(); } bool Document::guidesLocked() const { return d->document->guidesConfig().showGuides(); } Document *Document::clone() const { if (!d->document) return 0; QPointer clone = d->document->clone(); Document * d = new Document(clone); clone->setParent(d); // It's owned by the document, not KisPart return d; } void Document::setHorizontalGuides(const QList &lines) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform(); QList transformedLines; for (int i = 0; i< lines.size(); i++) { qreal line = lines[i]; transformedLines.append(transform.map(QPointF(line, line)).x()); } config.setHorizontalGuideLines(transformedLines); d->document->setGuidesConfig(config); } void Document::setVerticalGuides(const QList &lines) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform(); QList transformedLines; for (int i = 0; i< lines.size(); i++) { qreal line = lines[i]; transformedLines.append(transform.map(QPointF(line, line)).y()); } config.setVerticalGuideLines(transformedLines); d->document->setGuidesConfig(config); } void Document::setGuidesVisible(bool visible) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); config.setShowGuides(visible); d->document->setGuidesConfig(config); } void Document::setGuidesLocked(bool locked) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); config.setLockGuides(locked); d->document->setGuidesConfig(config); } bool Document::modified() const { if (!d->document) return false; return d->document->isModified(); } QRect Document::bounds() const { if (!d->document) return QRect(); return d->document->image()->bounds(); } QPointer Document::document() const { return d->document; } /* Animation related function */ bool Document::importAnimation(const QList &files, int firstFrame, int step) { KisView *activeView = KisPart::instance()->currentMainwindow()->activeView(); KoUpdaterPtr updater = 0; if (activeView && d->document->fileBatchMode()) { updater = activeView->viewManager()->createUnthreadedUpdater(i18n("Import frames")); } KisAnimationImporter importer(d->document->image(), updater); - KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); + KisImportExportErrorCode status = importer.import(files, firstFrame, step); - return (status == KisImportExportFilter::OK); + return status.isOk(); } int Document::framesPerSecond() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->framerate(); } void Document::setFramesPerSecond(int fps) { if (!d->document) return; if (!d->document->image()) return; d->document->image()->animationInterface()->setFramerate(fps); } void Document::setFullClipRangeStartTime(int startTime) { if (!d->document) return; if (!d->document->image()) return; d->document->image()->animationInterface()->setFullClipRangeStartTime(startTime); } int Document::fullClipRangeStartTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->fullClipRange().start(); } void Document::setFullClipRangeEndTime(int endTime) { if (!d->document) return; if (!d->document->image()) return; d->document->image()->animationInterface()->setFullClipRangeEndTime(endTime); } int Document::fullClipRangeEndTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->fullClipRange().end(); } int Document::animationLength() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->totalLength(); } void Document::setPlayBackRange(int start, int stop) { if (!d->document) return; if (!d->document->image()) return; const KisTimeRange newTimeRange = KisTimeRange(start, (stop-start)); d->document->image()->animationInterface()->setPlaybackRange(newTimeRange); } int Document::playBackStartTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->playbackRange().start(); } int Document::playBackEndTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->playbackRange().end(); } int Document::currentTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->currentTime(); } void Document::setCurrentTime(int time) { if (!d->document) return; if (!d->document->image()) return; return d->document->image()->animationInterface()->requestTimeSwitchWithUndo(time); } diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 3202763d35..1ec57f07b7 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,611 +1,612 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ) add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) find_library(APPKIT_LIBRARY AppKit) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_paintop_transformation_connector.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp canvas/KisSnapPointStrategy.cpp canvas/KisSnapPixelStrategy.cpp canvas/KisMirrorAxisConfig.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_stroke_selection_properties.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_blacklist_cleanup.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/KisSessionManagerDialog.cpp dialogs/KisNewWindowLayoutDialog.cpp dialogs/KisDlgChangeCloneSource.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp brushhud/kis_uniform_paintop_property_widget.cpp brushhud/kis_brush_hud.cpp brushhud/kis_round_hud_button.cpp brushhud/kis_dlg_brush_hud_config.cpp brushhud/kis_brush_hud_properties_list.cpp brushhud/kis_brush_hud_properties_config.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc KisPaintopPropertiesBase.cpp kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc KisOcioConfiguration.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_cursor.cc kis_cursor_cache.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_change_file_layer_command.h kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc KisImageBarrierLockerWithFeedback.cpp kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp KisNodeDisplayModeAdapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp KisDecorationsManager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp KisResourceServerProvider.cpp KisResourceBundleServerProvider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc KisSelectionActionsAdapter.cpp kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp KisActionPlugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp KisWelcomePageWidget.cpp KisChangeCloneLayersCommand.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp opengl/kis_texture_tile_info_pool.cpp opengl/KisOpenGLUpdateInfoBuilder.cpp opengl/KisOpenGLModeProber.cpp opengl/KisScreenInformationAdapter.cpp kis_fps_decoration.cpp tool/KisToolChangesTracker.cpp tool/KisToolChangesTrackerData.cpp tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/KisStrokeSpeedMonitor.cpp tool/strokes/freehand_stroke.cpp tool/strokes/KisStrokeEfficiencyMeasurer.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp tool/strokes/KisFreehandStrokeInfo.cpp tool/strokes/KisMaskedFreehandStrokePainter.cpp tool/strokes/KisMaskingBrushRenderer.cpp tool/strokes/KisMaskingBrushCompositeOpFactory.cpp tool/strokes/move_stroke_strategy.cpp tool/KisSelectionToolFactoryBase.cpp tool/KisToolPaintFactoryBase.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_paintop_presets_save.cpp widgets/kis_paintop_preset_icon_library.cpp widgets/kis_pattern_chooser.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/kis_slider_spin_box.cpp widgets/KisSelectionPropertySlider.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp widgets/kis_elided_label.cpp widgets/kis_stopgradient_slider_widget.cpp widgets/kis_preset_live_preview_view.cpp widgets/KisScreenColorPicker.cpp widgets/KoDualColorButton.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp widgets/KisLayerStyleAngleSelector.cpp widgets/KisMemoryReportButton.cpp KisPaletteEditor.cpp dialogs/KisDlgPaletteEditor.cpp widgets/KisNewsWidget.cpp widgets/KisGamutMaskToolbar.cpp utils/kis_document_aware_spin_box_unit_manager.cpp utils/KisSpinBoxSplineUnitConverter.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_native_gesture_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp input/KisQtWidgetsTweaker.cpp input/KisInputActionGroup.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactory.cpp actions/KisTransformToolActivationCommand.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisCloneDocumentStroke.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisFilterEntry.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoActionsUpdateManager.cpp KisView.cpp KisImportExportErrorCode.cpp + KisImportExportAdditionalChecks.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisSaveGroupVisitor.cpp KisWindowLayoutResource.cpp KisWindowLayoutManager.cpp KisSessionResource.cpp KisReferenceImagesDecoration.cpp KisReferenceImage.cpp flake/KisReferenceImagesLayer.cpp flake/KisReferenceImagesLayer.h - + KisMouseClickEater.cpp ) if(WIN32) # Private headers are needed for: # * KisDlgCustomTabletResolution # * KisScreenInformationAdapter include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_win.cpp ) if (NOT USE_QT_TABLET_WINDOWS) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp input/wintab/kis_tablet_support_win8.cpp ) else() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} dialogs/KisDlgCustomTabletResolution.cpp ) endif() endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAsyncAnimationRendererBase.cpp KisAsyncAnimationCacheRenderer.cpp KisAsyncAnimationFramesSavingRenderer.cpp dialogs/KisAsyncAnimationRenderDialogBase.cpp dialogs/KisAsyncAnimationCacheRenderDialog.cpp dialogs/KisAsyncAnimationFramesSaveDialog.cpp canvas/kis_animation_player.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp KisFrameDataSerializer.cpp KisFrameCacheStore.cpp KisFrameCacheSwapper.cpp KisAbstractFrameCacheSwapper.cpp KisInMemoryFrameCacheSwapper.cpp input/wintab/drawpile_tablettester/tablettester.cpp input/wintab/drawpile_tablettester/tablettest.cpp ) if (UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_unix.cpp ) endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} osx.mm ) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgsavebrushpreset.ui forms/wdgpreseticonlibrary.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui forms/wdgsessionmanager.ui forms/wdgnewwindowlayout.ui forms/KisWelcomePage.ui forms/WdgDlgPaletteEditor.ui forms/KisNewsPage.ui forms/wdgGamutMaskToolbar.ui forms/wdgchangeclonesource.ui brushhud/kis_dlg_brush_hud_config.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui layerstyles/wdgKisLayerStyleAngleSelector.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui input/wintab/drawpile_tablettester/tablettest.ui ) if(WIN32) if(USE_QT_TABLET_WINDOWS) ki18n_wrap_ui(kritaui_LIB_SRCS dialogs/KisDlgCustomTabletResolution.ui ) else() ki18n_wrap_ui(kritaui_LIB_SRCS input/wintab/kis_screen_size_choice_dialog.ui ) endif() endif() add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} LibExiv2::LibExiv2 ) if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) target_link_libraries(kritaui ${APPKIT_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_include_directories(kritaui PUBLIC $ $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index ba5fdd2495..de37745e85 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,859 +1,873 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_MACOS #include "osx.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoConfig.h" #include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include "KisViewManager.h" #include "kis_workspace_resource.h" #include #include #include "widgets/KisScreenColorPicker.h" #include "KisDlgInternalColorSelector.h" #include #include namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplication::Private { public: Private() {} QPointer splashScreen; KisAutoSaveRecoveryDialog *autosaveDialog {0}; QPointer mainWindow; // The first mainwindow we create on startup bool batchRun {false}; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { m_splash->hide(); } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new Private) { #ifdef Q_OS_MACOS setMouseCoalescingEnabled(false); #endif QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("calligrakrita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { QStringList styles = QStringList() /*<< "breeze"*/ << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } KisOpenGL::initialize(); } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { // qDebug() << "addResourceTypes();"; // All Krita's resource types KoResourcePaths::addResourceType("markers", "data", "/styles/"); KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/"); KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/"); KoResourcePaths::addResourceType("kis_windowlayouts", "data", "/windowlayouts/"); KoResourcePaths::addResourceType("kis_sessions", "data", "/sessions/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/"); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true); KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType("symbols", "data", "/symbols"); KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); KoResourcePaths::addResourceType("ko_gamutmasks", "data", "/gamutmasks/", true); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/brushes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gamutmasks/"); } void KisApplication::loadResources() { // qDebug() << "loadResources();"; setSplashScreenLoadingText(i18n("Loading Resources...")); processEvents(); KoResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brush Presets...")); processEvents(); KisResourceServerProvider::instance(); setSplashScreenLoadingText(i18n("Loading Brushes...")); processEvents(); KisBrushServer::instance()->brushServer(); setSplashScreenLoadingText(i18n("Loading Bundles...")); processEvents(); KisResourceBundleServerProvider::instance(); } void KisApplication::loadResourceTags() { // qDebug() << "loadResourceTags()"; KoResourceServerProvider::instance()->patternServer()->loadTags(); KoResourceServerProvider::instance()->gradientServer()->loadTags(); KoResourceServerProvider::instance()->paletteServer()->loadTags(); KoResourceServerProvider::instance()->svgSymbolCollectionServer()->loadTags(); KisBrushServer::instance()->brushServer()->loadTags(); KisResourceServerProvider::instance()->workspaceServer()->loadTags(); KisResourceServerProvider::instance()->layerStyleCollectionServer()->loadTags(); KisResourceBundleServerProvider::instance()->resourceBundleServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->loadTags(); KisResourceServerProvider::instance()->paintOpPresetServer()->clearOldSystemTags(); } void KisApplication::loadPlugins() { // qDebug() << "loadPlugins();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); } void KisApplication::loadGuiPlugins() { // qDebug() << "loadGuiPlugins();"; // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); // qDebug() << "loading tools"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); processEvents(); // qDebug() << "loading dockers"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); processEvents(); // qDebug() << "loading exiv2"; KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg(false); #if defined(Q_OS_WIN) #ifdef ENV32BIT if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); const bool exportAs = args.exportAs(); const bool exportSequence = args.exportSequence(); const QString exportFileName = args.exportFileName(); d->batchRun = (exportAs || exportSequence || !exportFileName.isEmpty()); const bool needsMainWindow = (!exportAs && !exportSequence); // only show the mainWindow when no command-line mode option is passed bool showmainWindow = (!exportAs && !exportSequence); // would be !batchRun; const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load the plugins loadPlugins(); // Load all resources loadResources(); // Load all the tags loadResourceTags(); // Load the gui plugins loadGuiPlugins(); KisPart *kisPart = KisPart::instance(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); bool sessionNeeded = true; auto sessionMode = cfg.sessionOnStartup(); if (!args.session().isEmpty()) { sessionNeeded = !kisPart->restoreSession(args.session()); } else if (sessionMode == KisConfig::SOS_ShowSessionManager) { showmainWindow = false; sessionNeeded = false; kisPart->showSessionManager(); } else if (sessionMode == KisConfig::SOS_PreviousSession) { KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session"); const QString &sessionName = sessionCfg.readEntry("previousSession"); sessionNeeded = !kisPart->restoreSession(sessionName); } if (sessionNeeded) { kisPart->startBlankSession(); } if (!args.windowLayout().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResource* windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(args.workspace()); if (workspace) { d->mainWindow->restoreWorkspace(workspace); } } if (args.canvasOnly()) { d->mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { d->mainWindow->showFullScreen(); } } else { d->mainWindow = kisPart->createMainWindow(); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test) if (!d->batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.image(); if (doc) { kisPart->addDocument(doc); d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (d->batchRun) { continue; } if (createNewDocFromTemplate(fileName, d->mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return 1; } KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); - doc->openUrl(QUrl::fromLocalFile(fileName)); + bool result = doc->openUrl(QUrl::fromLocalFile(fileName)); + + if (!result) { + errKrita << "Could not load " << fileName << ":" << doc->errorMessage(); + QTimer::singleShot(0, this, SLOT(quit())); + return 1; + } + + if (exportFileName.isEmpty()) { + errKrita << "Export destination is not specified for" << fileName << "Please specify export destination with --export-filename option"; + QTimer::singleShot(0, this, SLOT(quit())); + return 1; + } qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { - dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); + errKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } QTimer::singleShot(0, this, SLOT(quit())); + return 0; } else if (exportSequence) { KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated if (!doc->image()->animationInterface()->hasAnimation()) { errKrita << "This file has no animation." << endl; QTimer::singleShot(0, this, SLOT(quit())); return 1; } doc->setFileBatchMode(true); int sequenceStart = 0; KisAsyncAnimationFramesSaveDialog exporter(doc->image(), doc->image()->animationInterface()->fullClipRange(), exportFileName, sequenceStart, 0); exporter.setBatchMode(d->batchRun); KisAsyncAnimationFramesSaveDialog::Result result = exporter.regenerateRange(0); if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) { errKrita << i18n("Failed to render animation frames!") << endl; } QTimer::singleShot(0, this, SLOT(quit())); + return 0; } else if (d->mainWindow) { if (fileName.endsWith(".bundle")) { d->mainWindow->installBundle(fileName); } else { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { // Normal case, success numberOfOpenDocuments++; } } } } } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(true); d->splashScreen->displayRecentFiles(true); } // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(QString textToLoad) { if (d->splashScreen) { //d->splashScreen->loadingLabel->setText(textToLoad); d->splashScreen->setLoadingText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { return; } KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { createNewDocFromTemplate(filename, mw); } else if (QFile(filename).exists()) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mw->openDocument(QUrl::fromLocalFile(filename), flags); } } } } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } void KisApplication::checkAutosaveFiles() { if (d->batchRun) return; #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! // Hidden autosave files QStringList filters = QStringList() << QString(".krita-*-*-autosave.kra"); // all autosave files for our application QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Visibile autosave files filters = QStringList() << QString("krita-*-*-autosave.kra"); autosaveFiles += dir.entryList(filters, QDir::Files); // Allow the user to make their selection if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec(); if (result == QDialog::Accepted) { QStringList filesToRecover = d->autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } autosaveFiles = filesToRecover; } else { autosaveFiles.clear(); } if (autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } if (d->mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // cleanup delete d->autosaveDialog; d->autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); if (templateURL.scheme().isEmpty()) { templateURL.setScheme("file"); } KisMainWindow::OpenFlags batchFlags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) { dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp index 539a7cb4db..b4b6c8f510 100644 --- a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp +++ b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp @@ -1,127 +1,127 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisAsyncAnimationFramesSavingRenderer.h" #include "kis_image.h" #include "kis_paint_device.h" #include "KisImportExportFilter.h" #include "KisPart.h" #include "KisDocument.h" #include "kis_time_range.h" #include "kis_paint_layer.h" struct KisAsyncAnimationFramesSavingRenderer::Private { Private(KisImageSP image, const KisTimeRange &_range, int _sequenceNumberingOffset, KisPropertiesConfigurationSP _exportConfiguration) : savingDoc(KisPart::instance()->createDocument()), range(_range), sequenceNumberingOffset(_sequenceNumberingOffset), exportConfiguration(_exportConfiguration) { savingDoc->setInfiniteAutoSaveInterval(); savingDoc->setFileBatchMode(true); KisImageSP savingImage = new KisImage(savingDoc->createUndoStore(), image->bounds().width(), image->bounds().height(), image->colorSpace(), QString()); savingImage->setResolution(image->xRes(), image->yRes()); savingDoc->setCurrentImage(savingImage); KisPaintLayer* paintLayer = new KisPaintLayer(savingImage, "paint device", 255); savingImage->addNode(paintLayer, savingImage->root(), KisLayerSP(0)); savingDevice = paintLayer->paintDevice(); } QScopedPointer savingDoc; KisPaintDeviceSP savingDevice; KisTimeRange range; int sequenceNumberingOffset = 0; QString filenamePrefix; QString filenameSuffix; QByteArray outputMimeType; KisPropertiesConfigurationSP exportConfiguration; }; KisAsyncAnimationFramesSavingRenderer::KisAsyncAnimationFramesSavingRenderer(KisImageSP image, const QString &fileNamePrefix, const QString &fileNameSuffix, const QByteArray &outputMimeType, const KisTimeRange &range, const int sequenceNumberingOffset, KisPropertiesConfigurationSP exportConfiguration) : m_d(new Private(image, range, sequenceNumberingOffset, exportConfiguration)) { m_d->filenamePrefix = fileNamePrefix; m_d->filenameSuffix = fileNameSuffix; m_d->outputMimeType = outputMimeType; connect(this, SIGNAL(sigCompleteRegenerationInternal(int)), SLOT(notifyFrameCompleted(int))); connect(this, SIGNAL(sigCancelRegenerationInternal(int)), SLOT(notifyFrameCancelled(int))); } KisAsyncAnimationFramesSavingRenderer::~KisAsyncAnimationFramesSavingRenderer() { } void KisAsyncAnimationFramesSavingRenderer::frameCompletedCallback(int frame, const QRegion &requestedRegion) { KisImageSP image = requestedImage(); if (!image) return; KIS_SAFE_ASSERT_RECOVER (requestedRegion == image->bounds()) { emit sigCancelRegenerationInternal(frame); return; } m_d->savingDevice->makeCloneFromRough(image->projection(), image->bounds()); - KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK; + KisImportExportErrorCode status = ImportExportCodes::OK; QString frameNumber = QString("%1").arg(frame + m_d->sequenceNumberingOffset, 4, 10, QChar('0')); QString filename = m_d->filenamePrefix + frameNumber + m_d->filenameSuffix; if (!m_d->savingDoc->exportDocumentSync(QUrl::fromLocalFile(filename), m_d->outputMimeType, m_d->exportConfiguration)) { - status = KisImportExportFilter::InternalError; + status = ImportExportCodes::InternalError; } - if (status == KisImportExportFilter::OK) { + if (status.isOk()) { emit sigCompleteRegenerationInternal(frame); } else { emit sigCancelRegenerationInternal(frame); } } void KisAsyncAnimationFramesSavingRenderer::frameCancelledCallback(int frame) { notifyFrameCancelled(frame); } diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 859b082e3a..391c8c368f 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1996 +1,2005 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" #include "KisCloneDocumentStroke.h" #include // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *q) : docInfo(new KoDocumentInfo(q)) // deleted by QObject , importExportManager(new KisImportExportManager(q)) // deleted manually , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , globalAssistantsColor(KisConfig(true).defaultAssistantsColor()) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *q) : docInfo(new KoDocumentInfo(*rhs.docInfo, q)) , unit(rhs.unit) , importExportManager(new KisImportExportManager(q)) , mimeType(rhs.mimeType) , outputMimeType(rhs.outputMimeType) , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) , guidesConfig(rhs.guidesConfig) , mirrorAxisConfig(rhs.mirrorAxisConfig) , m_bAutoDetectedMime(rhs.m_bAutoDetectedMime) , m_url(rhs.m_url) , m_file(rhs.m_file) , modified(rhs.modified) , readwrite(rhs.readwrite) , firstMod(rhs.firstMod) , lastMod(rhs.lastMod) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document! , globalAssistantsColor(rhs.globalAssistantsColor) , paletteList(rhs.paletteList) , gridConfig(rhs.gridConfig) , savingLock(&savingMutex) , batchMode(rhs.batchMode) { // TODO: clone assistants } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; KisMirrorAxisConfig mirrorAxisConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; QColor globalAssistantsColor; KisSharedPtr referenceImagesLayer; QList paletteList; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; - QFuture childSavingFuture; + QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class StrippedSafeSavingLocker; }; class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(rhs.objectName()); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(true), false); if (rhs.d->preActivatedNode) { // since we clone uuid's, we can use them for lacating new // nodes. Otherwise we would need to use findSymmetricClone() d->preActivatedNode = KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid()); } } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { - slotCompleteSavingDocument(job, - KisImportExportFilter::CreationError, + slotCompleteSavingDocument(job, ImportExportCodes::NoAccessToWrite, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); + //return ImportExportCodes::NoAccessToWrite; return false; } KisConfig cfg(true); if (cfg.backupFile() && filePathInfo.exists()) { QString backupDir; switch(cfg.readEntry("backupfilelocation", 0)) { case 1: backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); break; case 2: backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); break; default: // Do nothing: the empty string is user file location break; } int numOfBackupsKept = cfg.readEntry("numberofbackupfiles", 1); QString suffix = cfg.readEntry("backupfilesuffix", "~"); if (numOfBackupsKept == 1) { KBackup::simpleBackupFile(job.filePath, backupDir, suffix); } else if (numOfBackupsKept > 2) { KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept); } } - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); + //KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); + if (job.mimeType.isEmpty()) { + + KisImportExportErrorCode error = ImportExportCodes::FileFormatIncorrect; + slotCompleteSavingDocument(job, error, error.errorMessage()); + return false; + + } const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); + ENTER_FUNCTION() << "second!"; bool started = initiateSavingInBackground(actionName, - this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), + this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode ,QString)), job, exportConfiguration); - + ENTER_FUNCTION() << "started? " << started; if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8") .arg(url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")); return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, %7 framerate. Export configuration: %8") .arg(_url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration") .arg(url().toLocalFile())); return exportDocumentImpl(ExportFileJob(_url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; - if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { + if (!filter->convert(this, &buffer).isOk()) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } -void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) +void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { - if (status == KisImportExportFilter::UserCancelled) + if (status.isCancelled()) return; const QString fileName = QFileInfo(job.filePath).fileName(); - if (status != KisImportExportFilter::OK) { + ENTER_FUNCTION() << "status = " << status << " Message " << status.errorMessage(); + + if (!status.isOk()) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage))); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { const QString existingAutoSaveBaseName = localFilePath(); const bool wasRecovered = isRecovered(); setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { /** * If undo stack is already clean/empty, it doesn't emit any * signals, so we might forget update document modified state * (which was set, e.g. while recovering an autosave file) */ if (d->undoStack->isClean()) { setModified(false); } else { d->undoStack->setClean(); } } setRecovered(false); removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning QApplication::processEvents(); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { { /** * The caller guarantees that noone else uses the document (usually, * it is a temporary docuent created specifically for exporting), so * we don't need to copy or lock the document. Instead we should just * ensure the barrier lock is synced and then released. */ Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } } d->savingImage = d->image; const QString fileName = url.toLocalFile(); - KisImportExportFilter::ConversionStatus status = + KisImportExportErrorCode status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; - return status == KisImportExportFilter::OK; + return status.isOk(); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } auto waitForImage = [] (KisImageSP image) { KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { window->viewManager()->blockUntilOperationsFinishedForced(image); } } }; { KisNodeSP newRoot = clonedDocument->image()->root(); KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) { KisLayerUtils::forceAllDelayedNodesUpdate(newRoot); waitForImage(clonedDocument->image()); } } KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) { waitForImage(clonedDocument->image()); } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), - SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus,QString)), + SIGNAL(sigBackgroundSavingFinished(KisImportExportErrorCode, QString)), this, - SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus,QString))); + SLOT(slotChildCompletedSavingInBackground(KisImportExportErrorCode, QString))); - connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), + connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } -void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) +void KisDocument::slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage) { KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { d->savingMutex.unlock(); return; } KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3") .arg(job.filePath) .arg(QString::fromLatin1(job.mimeType)) - .arg(status != KisImportExportFilter::OK ? exportErrorToUserMessage(status, errorMessage) : "OK")); + .arg(!status.isOk() ? exportErrorToUserMessage(status, errorMessage) : "OK")); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), - this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), + this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } -void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) +void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); - if (status != KisImportExportFilter::OK) { + if (!status.isOk()) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg(true); d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } - KisImportExportFilter::ConversionStatus initializationStatus; + KisImportExportErrorCode initializationStatus(ImportExportCodes::OK); d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); - if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) { + if (!initializationStatus.isOk()) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); - emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage()); + emit sigBackgroundSavingFinished(initializationStatus, initializationStatus.errorMessage()); return false; } - typedef QFutureWatcher StatusWatcher; + typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { - emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, ""); + emit sigBackgroundSavingFinished(ImportExportCodes::InternalError, ""); return; } - KisImportExportFilter::ConversionStatus status = + KisImportExportErrorCode status = d->childSavingFuture.result(); - const QString errorMessage = this->errorMessage(); + const QString errorMessage = status.errorMessage(); d->savingImage.clear(); - d->childSavingFuture = QFuture(); + d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QString prefix = KisConfig(true).readEntry("autosavefileshidden") ? QString(".") : QString(); QRegularExpression autosavePattern1("^\\..+-autosave.kra$"); QRegularExpression autosavePattern2("^.+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #endif } else { retval = QString("%1%2%5%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension).arg(prefix); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (ret) { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

"; if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); KoUpdaterPtr updater; if (window && window->viewManager()) { updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } - KisImportExportFilter::ConversionStatus status; - - status = d->importExportManager->importDocument(localFilePath(), typeName); + KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName); - if (status != KisImportExportFilter::OK) { + if (!status.isOk()) { if (window && window->viewManager()) { updater->cancel(); } - QString msg = KisImportExportFilter::conversionStatusString(status); + QString msg = status.errorMessage(); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); undoStack()->clear(); return true; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered) { //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { //qDebug() << "\tremoving autosavefile" << asf; QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME //qDebug() << "Autsavefile in $home" << asf; if (QFile::exists(asf)) { //qDebug() << "\tremoving autsavefile 2" << asf; QFile::remove(asf); } QList expressions; expressions << QRegularExpression("^\\..+-autosave.kra$") << QRegularExpression("^.+-autosave.kra$"); Q_FOREACH(const QRegularExpression &rex, expressions) { if (wasRecovered && !autosaveBaseName.isEmpty() && rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() && QFile::exists(autosaveBaseName)) { QFile::remove(autosaveBaseName); } } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg(true); if (!d->undoStack->isClean() && d->undoStack->undoLimit() != cfg.undoStackLimit()) { d->undoStack->clear(); d->undoStack->setUndoLimit(cfg.undoStackLimit()); } d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } QList &KisDocument::paletteList() { return d->paletteList; } void KisDocument::setPaletteList(const QList &paletteList) { d->paletteList = paletteList; } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const { return d->mirrorAxisConfig; } void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config) { if (d->mirrorAxisConfig == config) { return; } d->mirrorAxisConfig = config; setModified(true); emit sigMirrorAxisConfigChanged(); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) { return false; } if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) { return false; } d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisImageSP image; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); documentInfo()->setAboutInfo("abstract", description); KisLayerSP layer; if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) { KoColor strippedAlpha = bgColor; strippedAlpha.setOpacity(OPACITY_OPAQUE_U8); if (bgStyle == KisConfig::RASTER_LAYER) { layer = new KisPaintLayer(image.data(), "Background", OPACITY_OPAQUE_U8, cs);; layer->paintDevice()->setDefaultPixel(strippedAlpha); } else if (bgStyle == KisConfig::FILL_LAYER) { KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration(); filter_config->setProperty("color", strippedAlpha.toQColor()); layer = new KisGeneratorLayer(image.data(), "Background Fill", filter_config, image->globalSelection()); } layer->setOpacity(bgColor.opacityU8()); if (numberOfLayers > 1) { //Lock bg layer if others are present. layer->setUserLocked(true); } } else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer). image->setDefaultProjectionColor(bgColor); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); } Q_CHECK_PTR(layer); image->addNode(layer.data(), image->rootLayer().data()); layer->setDirty(QRect(0, 0, width, height)); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } KisConfig cfg(false); cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); KisUsageLogger::log(i18n("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8" , name , width, height , imageResolution * 72.0 , image->colorSpace()->colorModelId().name(), image->colorSpace()->colorDepthId().name() , image->colorSpace()->profile()->name() , numberOfLayers)); QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { if (isSaving()) { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } } KoShapeControllerBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList &value) { d->assistants = value; } KisSharedPtr KisDocument::referenceImagesLayer() const { return d->referenceImagesLayer.data(); } void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage) { if (d->referenceImagesLayer) { d->referenceImagesLayer->disconnect(this); } if (updateImage) { if (layer) { d->image->addNode(layer); } else { d->image->removeNode(d->referenceImagesLayer); } } d->referenceImagesLayer = layer; if (d->referenceImagesLayer) { connect(d->referenceImagesLayer, SIGNAL(sigUpdateCanvas(QRectF)), this, SIGNAL(sigReferenceImagesChanged())); } } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) { if (d->image) { // Disconnect existing sig/slot connections d->image->setUndoStore(new KisDumbUndoStore()); d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->image->setUndoStore(new KisDocumentUndoStore(this)); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); if (forceInitialUpdate) { d->image->initialRefreshGraph(); } } void KisDocument::hackPreliminarySetImage(KisImageSP image) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); } void KisDocument::setImageModified() { setModified(true); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } -QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) +QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage) { - return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage; + return errorMessage.isEmpty() ? status.errorMessage() : errorMessage; } void KisDocument::setAssistantsGlobalColor(QColor color) { d->globalAssistantsColor = color; } QColor KisDocument::assistantsGlobalColor() { return d->globalAssistantsColor; } QRectF KisDocument::documentBounds() const { QRectF bounds = d->image->bounds(); if (d->referenceImagesLayer) { bounds |= d->referenceImagesLayer->boundingImageRect(); } return bounds; } diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h index 01e5f59938..2ccdbfeb64 100644 --- a/libs/ui/KisDocument.h +++ b/libs/ui/KisDocument.h @@ -1,667 +1,667 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISDOCUMENT_H #define KISDOCUMENT_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kritaui_export.h" #include class QString; class KUndo2Command; class KoUnit; class KoColor; class KoColorSpace; class KoShapeControllerBase; class KoShapeLayer; class KoStore; class KoOdfReadStore; class KoDocumentInfo; class KoDocumentInfoDlg; class KisImportExportManager; class KisUndoStore; class KisPart; class KisGridConfig; class KisGuidesConfig; class KisMirrorAxisConfig; class QDomDocument; class KisReferenceImagesLayer; #define KIS_MIME_TYPE "application/x-krita" /** * The %Calligra document class * * This class provides some functionality each %Calligra document should have. * * @short The %Calligra document class */ class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase { Q_OBJECT protected: explicit KisDocument(); /** * @brief KisDocument makes a deep copy of the document \p rhs. * The caller *must* ensure that the image is properly * locked and is in consistent state before asking for * cloning. * @param rhs the source document to copy from */ explicit KisDocument(const KisDocument &rhs); public: enum OpenFlag { None = 0, DontAddToRecent = 0x1, RecoveryFile = 0x2 }; Q_DECLARE_FLAGS(OpenFlags, OpenFlag) /** * Destructor. * * The destructor does not delete any attached KisView objects and it does not * delete the attached widget as returned by widget(). */ ~KisDocument() override; /** * @brief reload Reloads the document from the original url * @return the result of loading the document */ bool reload(); /** * @brief creates a clone of the document and returns it. Please make sure that you * hold all the necessary locks on the image before asking for a clone! */ KisDocument* clone(); /** * @brief openUrl Open an URL * @param url The URL to open * @param flags Control specific behavior * @return success status */ bool openUrl(const QUrl &url, OpenFlags flags = None); /** * Opens the document given by @p url, without storing the URL * in the KisDocument. * Call this instead of openUrl() to implement KisMainWindow's * File --> Import feature. * * @note This will call openUrl(). To differentiate this from an ordinary * Open operation (in any reimplementation of openUrl() or openFile()) * call isImporting(). */ bool importDocument(const QUrl &url); /** * Saves the document as @p url without changing the state of the * KisDocument (URL, modified flag etc.). Call this instead of * KisParts::ReadWritePart::saveAs() to implement KisMainWindow's * File --> Export feature. */ bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0); /** * Exports he document is a synchronous way. The caller must ensure that the * image is not accessed by any other actors, because the exporting happens * without holding the image lock. */ bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0); private: bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); public: /** * @brief Sets whether the document can be edited or is read only. * * This recursively applied to all child documents and * KisView::updateReadWrite is called for every attached * view. */ void setReadWrite(bool readwrite = true); /** * To be preferred when a document exists. It is fast when calling * it multiple times since it caches the result that readNativeFormatMimeType() * delivers. * This comes from the X-KDE-NativeMimeType key in the .desktop file. */ static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; } /// Checks whether a given mimetype can be handled natively. bool isNativeFormat(const QByteArray& mimetype) const; /// Returns a list of the mimetypes considered "native", i.e. which can /// be saved by KisDocument without a filter, in *addition* to the main one static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; } /** * Returns the actual mimetype of the document */ QByteArray mimeType() const override; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ void setMimeType(const QByteArray & mimeType) override; /** * @return true if file operations should inhibit the option dialog */ bool fileBatchMode() const; /** * @param batchMode if true, do not show the option dialog for file operations. */ void setFileBatchMode(const bool batchMode); /** * Sets the error message to be shown to the user (use i18n()!) * when loading or saving fails. * If you asked the user about something and they chose "Cancel", */ void setErrorMessage(const QString& errMsg); /** * Return the last error message. Usually KisDocument takes care of * showing it; this method is mostly provided for non-interactive use. */ QString errorMessage() const; /** * Sets the warning message to be shown to the user (use i18n()!) * when loading or saving fails. */ void setWarningMessage(const QString& warningMsg); /** * Return the last warning message set by loading or saving. Warnings * mean that the document could not be completely loaded, but the errors * were not absolutely fatal. */ QString warningMessage() const; /** * @brief Generates a preview picture of the document * @note The preview is used in the File Dialog and also to create the Thumbnail */ QPixmap generatePreview(const QSize& size); /** * Tells the document that its title has been modified, either because * the modified status changes (this is done by setModified() ) or * because the URL or the document-info's title changed. */ void setTitleModified(); /** * @brief Sets the document to empty. * * Used after loading a template * (which is not empty, but not the user's input). * * @see isEmpty() */ void setEmpty(bool empty = true); /** * Return a correctly created QDomDocument for this KisDocument, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * @param tagName the name of the tag for the root element * @param version the DTD version (usually the application's version). */ QDomDocument createDomDocument(const QString& tagName, const QString& version) const; /** * Return a correctly created QDomDocument for an old (1.3-style) %Calligra document, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * This static method can be used e.g. by filters. * @param appName the app's instance name, e.g. words, kspread, kpresenter etc. * @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter. * @param version the DTD version (usually the application's version). */ static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version); /** * Loads a document in the native format from a given URL. * Reimplement if your native format isn't XML. * * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter */ bool loadNativeFormat(const QString & file); /** * Set standard autosave interval that is set by a config file */ void setNormalAutoSaveInterval(); /** * Set emergency interval that autosave uses when the image is busy, * by default it is 10 sec */ void setEmergencyAutoSaveInterval(); /** * Disable autosave */ void setInfiniteAutoSaveInterval(); /** * @return the information concerning this document. * @see KoDocumentInfo */ KoDocumentInfo *documentInfo() const; /** * Performs a cleanup of unneeded backup files */ void removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered); /** * Returns true if this document or any of its internal child documents are modified. */ bool isModified() const override; /** * @return caption of the document * * Caption is of the form "[title] - [url]", * built out of the document info (title) and pretty-printed * document URL. * If the title is not present, only the URL it returned. */ QString caption() const; /** * Sets the document URL to empty URL * KParts doesn't allow this, but %Calligra apps have e.g. templates * After using loadNativeFormat on a template, one wants * to set the url to QUrl() */ void resetURL(); /** * @internal (public for KisMainWindow) */ void setMimeTypeAfterLoading(const QString& mimeType); /** * Returns the unit used to display all measures/distances. */ KoUnit unit() const; /** * Sets the unit used to display all measures/distances. */ void setUnit(const KoUnit &unit); KisGridConfig gridConfig() const; void setGridConfig(const KisGridConfig &config); /// returns the guides data for this document. const KisGuidesConfig& guidesConfig() const; void setGuidesConfig(const KisGuidesConfig &data); const KisMirrorAxisConfig& mirrorAxisConfig() const; void setMirrorAxisConfig(const KisMirrorAxisConfig& config); QList &paletteList(); void setPaletteList(const QList &paletteList); void clearUndoHistory(); /** * Sets the modified flag on the document. This means that it has * to be saved or not before deleting it. */ void setModified(bool _mod); void setRecovered(bool value); bool isRecovered() const; void updateEditingTime(bool forceStoreElapsed); /** * Returns the global undo stack */ KUndo2Stack *undoStack(); /** * @brief importExportManager gives access to the internal import/export manager * @return the document's import/export manager */ KisImportExportManager *importExportManager() const; /** * @brief serializeToNativeByteArray daves the document into a .kra file wtitten * to a memory-based byte-array * @return a byte array containing the .kra file */ QByteArray serializeToNativeByteArray(); /** * @brief isInSaving shown if the document has any (background) saving process or not * @return true if there is some saving in action */ bool isInSaving() const; public Q_SLOTS: /** * Adds a command to the undo stack and executes it by calling the redo() function. * @param command command to add to the undo stack */ void addCommand(KUndo2Command *command); /** * Begins recording of a macro command. At the end endMacro needs to be called. * @param text command description */ void beginMacro(const KUndo2MagicString &text); /** * Ends the recording of a macro command. */ void endMacro(); Q_SIGNALS: /** * This signal is emitted when the unit is changed by setUnit(). * It is common to connect views to it, in order to change the displayed units * (e.g. in the rulers) */ void unitChanged(const KoUnit &unit); /** * Emitted e.g. at the beginning of a save operation * This is emitted by KisDocument and used by KisView to display a statusbar message */ void statusBarMessage(const QString& text, int timeout = 0); /** * Emitted e.g. at the end of a save operation * This is emitted by KisDocument and used by KisView to clear the statusbar message */ void clearStatusBarMessage(); /** * Emitted when the document is modified */ void modified(bool); void titleModified(const QString &caption, bool isModified); void sigLoadingFinished(); void sigSavingFinished(); void sigGuidesConfigChanged(const KisGuidesConfig &config); - void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + void sigBackgroundSavingFinished(KisImportExportErrorCode status, const QString &errorMessage); - void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage); void sigReferenceImagesChanged(); void sigMirrorAxisConfigChanged(); private Q_SLOTS: void finishExportInBackground(); - void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); - void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + void slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage); + void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage); - void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage); void slotInitiateAsyncAutosaving(KisDocument *clonedDocument); private: friend class KisPart; friend class SafeSavingLocker; bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument); bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); bool startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Activate/deactivate/configure the autosave feature. * @param delay in seconds, 0 to disable */ void setAutoSaveDelay(int delay); /** * Generate a name for the document. */ QString newObjectName(); QString generateAutoSaveFileName(const QString & path) const; /** * Loads a document * * Applies a filter if necessary, and calls loadNativeFormat in any case * You should not have to reimplement, except for very special cases. * * NOTE: this method also creates a new KisView instance! * * This method is called from the KReadOnlyPart::openUrl method. */ bool openFile(); public: bool isAutosaving() const override; public: QString localFilePath() const override; void setLocalFilePath( const QString &localFilePath ); KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const; bool isReadWrite() const; QUrl url() const override; void setUrl(const QUrl &url) override; bool closeUrl(bool promptToSave = true); bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0); /** * Create a new image that has this document as a parent and * replace the current image with this image. */ bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &imageDescription, const double imageResolution); bool isSaving() const; void waitForSavingToComplete(); KisImageWSP image() const; /** * @brief savingImage provides a detached, shallow copy of the original image that must be used when saving. * Any strokes in progress will not be applied to this image, so the result might be missing some data. On * the other hand, it won't block. * * @return a shallow copy of the original image, or 0 is saving is not in progress */ KisImageSP savingImage() const; /** * Set the current image to the specified image and turn undo on. */ void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true); /** * Set the image of the document preliminary, before the document * has completed loading. Some of the document items (shapes) may want * to access image properties (bounds and resolution), so we should provide * it to them even before the entire image is loaded. * * Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove * after it is deprecated. */ void hackPreliminarySetImage(KisImageSP image); KisUndoStore* createUndoStore(); /** * The shape controller matches internal krita image layers with * the flake shape hierarchy. */ KoShapeControllerBase * shapeController() const; KoShapeLayer* shapeForNode(KisNodeSP layer) const; /** * Set the list of nodes that was marked as currently active. Used *only* * for saving loading. Never use it for tools or processing. */ void setPreActivatedNode(KisNodeSP activatedNode); /** * @return the node that was set as active during loading. Used *only* * for saving loading. Never use it for tools or processing. */ KisNodeSP preActivatedNode() const; /// @return the list of assistants associated with this document QList assistants() const; /// @replace the current list of assistants with @param value void setAssistants(const QList &value); void setAssistantsGlobalColor(QColor color); QColor assistantsGlobalColor(); /** * Get existing reference images layer or null if none exists. */ KisSharedPtr referenceImagesLayer() const; void setReferenceImagesLayer(KisSharedPtr layer, bool updateImage); bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Return the bounding box of the image and associated elements (e.g. reference images) */ QRectF documentBounds() const; Q_SIGNALS: void completed(); void canceled(const QString &); private Q_SLOTS: void setImageModified(); void slotAutoSave(); void slotUndoStackCleanChanged(bool value); void slotConfigChanged(); private: /** * @brief try to clone the image. This method handles all the locking for you. If locking * has failed, no cloning happens * @return cloned document on success, null otherwise */ KisDocument *lockAndCloneForSaving(); - QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + QString exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage); QString prettyPathOrUrl() const; bool openUrlInternal(const QUrl &url); void slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument); class Private; Private *const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags) Q_DECLARE_METATYPE(KisDocument*) #endif diff --git a/libs/ui/KisImageBuilderResult.h b/libs/ui/KisImageBuilderResult.h index bbdab8e5d7..cbe8f74a25 100644 --- a/libs/ui/KisImageBuilderResult.h +++ b/libs/ui/KisImageBuilderResult.h @@ -1,44 +1,46 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_IMAGEBUILDER_RESULT_H #define KIS_IMAGEBUILDER_RESULT_H /** * Image import/export plugins can use these results to report about success or failure. */ enum KisImageBuilder_Result { KisImageBuilder_RESULT_FAILURE = -400, KisImageBuilder_RESULT_NOT_EXIST = -300, KisImageBuilder_RESULT_NOT_LOCAL = -200, KisImageBuilder_RESULT_BAD_FETCH = -100, KisImageBuilder_RESULT_INVALID_ARG = -50, KisImageBuilder_RESULT_OK = 0, KisImageBuilder_RESULT_PROGRESS = 1, KisImageBuilder_RESULT_CANCEL = 50, KisImageBuilder_RESULT_EMPTY = 100, KisImageBuilder_RESULT_BUSY = 150, KisImageBuilder_RESULT_NO_URI = 200, KisImageBuilder_RESULT_UNSUPPORTED = 300, KisImageBuilder_RESULT_INTR = 400, KisImageBuilder_RESULT_PATH = 500, KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE = 600 }; + + #endif diff --git a/plugins/impex/gif/kis_gif_import.h b/libs/ui/KisImportExportAdditionalChecks.cpp similarity index 59% copy from plugins/impex/gif/kis_gif_import.h copy to libs/ui/KisImportExportAdditionalChecks.cpp index 7b9d9f90bf..fa698a310c 100644 --- a/plugins/impex/gif/kis_gif_import.h +++ b/libs/ui/KisImportExportAdditionalChecks.cpp @@ -1,37 +1,40 @@ /* - * Copyright (c) 2018 Boudewijn Rempt + * Copyright (c) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -#ifndef _KIS_GIF_IMPORT_H_ -#define _KIS_GIF_IMPORT_H_ +#include "KisImportExportAdditionalChecks.h" +#include -#include -#include +bool KisImportExportAdditionalChecks::isFileWritable(QString filepath) +{ + QFileInfo finfo(filepath); + return finfo.isWritable(); +} + +bool KisImportExportAdditionalChecks::isFileReadable(QString filepath) +{ + QFileInfo finfo(filepath); + return finfo.isReadable(); +} -class KisGIFImport : public KisImportExportFilter +bool KisImportExportAdditionalChecks::doesFileExist(QString filepath) { - Q_OBJECT -public: - KisGIFImport(QObject *parent, const QVariantList &); - ~KisGIFImport() override; -public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; -}; + return QFile::exists(filepath); +} -#endif diff --git a/plugins/impex/pdf/kis_pdf_import.h b/libs/ui/KisImportExportAdditionalChecks.h similarity index 61% copy from plugins/impex/pdf/kis_pdf_import.h copy to libs/ui/KisImportExportAdditionalChecks.h index df387515ff..007c1c1a50 100644 --- a/plugins/impex/pdf/kis_pdf_import.h +++ b/libs/ui/KisImportExportAdditionalChecks.h @@ -1,37 +1,38 @@ /* - * Copyright (c) 2006 Cyrille Berger + * Copyright (c) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ +#ifndef KIS_IMPORT_EXPORT_ADDITIONAL_CHECKS_H +#define KIS_IMPORT_EXPORT_ADDITIONAL_CHECKS_H -#ifndef KIS_PDF_IMPORT_H -#define KIS_PDF_IMPORT_H +#include +#include -#include - -#include - -class KisPDFImport : public KisImportExportFilter +class KRITAUI_EXPORT KisImportExportAdditionalChecks { - Q_OBJECT -public: - KisPDFImport(QObject *parent, const QVariantList &); - virtual ~KisPDFImport(); + public: - virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0); + + static bool isFileWritable(QString filepath); + static bool isFileReadable(QString filepath); + static bool doesFileExist(QString filepath); }; -#endif + + + +#endif // #ifndef KIS_IMPORT_EXPORT_ADDITIONAL_CHECKS_H diff --git a/libs/ui/KisImportExportErrorCode.cpp b/libs/ui/KisImportExportErrorCode.cpp index 5874351653..8fbb022d77 100644 --- a/libs/ui/KisImportExportErrorCode.cpp +++ b/libs/ui/KisImportExportErrorCode.cpp @@ -1,189 +1,224 @@ /* * Copyright (c) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisImportExportErrorCode.h" #include #include KisImportExportComplexError::KisImportExportComplexError(QFileDevice::FileError error) : m_error(error) { } QString KisImportExportComplexError::qtErrorMessage() const { // Error descriptions in most cases taken from https://doc.qt.io/qt-5/qfiledevice.html QString unspecifiedError = i18n("An unspecified error occurred."); switch (m_error) { case QFileDevice::FileError::NoError : // Returning this file error may mean that something is wrong in our code. // Successful operation should return ImportExportCodes::OK instead. return i18n("The action has been completed successfully."); case QFileDevice::FileError::ReadError : return i18n("An error occurred when reading from the file."); case QFileDevice::FileError::WriteError : return i18n("An error occurred when writing to the file."); case QFileDevice::FileError::FatalError : return i18n("A fatal error occurred."); case QFileDevice::FileError::ResourceError : return i18n("Out of resources (e.g. out of memory)."); case QFileDevice::FileError::OpenError : return i18n("The file could not be opened."); case QFileDevice::FileError::AbortError : return i18n("The operation was aborted."); case QFileDevice::FileError::TimeOutError : return i18n("A timeout occurred."); case QFileDevice::FileError::UnspecifiedError : return unspecifiedError; case QFileDevice::FileError::RemoveError : return i18n("The file could not be removed."); case QFileDevice::FileError::RenameError : return i18n("The file could not be renamed."); case QFileDevice::FileError::PositionError : return i18n("The position in the file could not be changed."); case QFileDevice::FileError::ResizeError : return i18n("The file could not be resized."); case QFileDevice::FileError::PermissionsError : return i18n("Permission denied. Krita is not allowed to read or write to the file."); case QFileDevice::FileError::CopyError : return i18n("The file could not be copied."); } return unspecifiedError; } +KisImportExportErrorCannotRead::KisImportExportErrorCannotRead() : KisImportExportComplexError(QFileDevice::FileError()) { } KisImportExportErrorCannotRead::KisImportExportErrorCannotRead(QFileDevice::FileError error) : KisImportExportComplexError(error) { KIS_ASSERT_RECOVER_NOOP(error != QFileDevice::NoError); } QString KisImportExportErrorCannotRead::errorMessage() const { return i18n("Cannot open file for reading. Reason: %1", qtErrorMessage()); } +bool KisImportExportErrorCannotRead::operator==(KisImportExportErrorCannotRead other) +{ + return other.m_error == m_error; +} + + + +KisImportExportErrorCannotWrite::KisImportExportErrorCannotWrite() : KisImportExportComplexError(QFileDevice::FileError()) { } KisImportExportErrorCannotWrite::KisImportExportErrorCannotWrite(QFileDevice::FileError error) : KisImportExportComplexError(error) { KIS_ASSERT_RECOVER_NOOP(error != QFileDevice::NoError); } QString KisImportExportErrorCannotWrite::errorMessage() const { return i18n("Cannot open file for writing. Reason: %1", qtErrorMessage()); } -KisImportExportErrorCode::KisImportExportErrorCode() : errorFieldUsed(None), cannotRead(QFileDevice::FileError()), cannotWrite(QFileDevice::FileError()) { } +bool KisImportExportErrorCannotWrite::operator==(KisImportExportErrorCannotWrite other) +{ + return other.m_error == m_error; +} + + -KisImportExportErrorCode::KisImportExportErrorCode(ImportExportCodes::ErrorCodeID id) : errorFieldUsed(CodeId), codeId(id), cannotRead(QFileDevice::FileError()), cannotWrite(QFileDevice::FileError()) { } -KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotRead error) : errorFieldUsed(CannotRead), cannotRead(error), cannotWrite(QFileDevice::FileError()) { } -KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotWrite error) : errorFieldUsed(CannotWrite), cannotRead(QFileDevice::FileError()), cannotWrite(error) { } +KisImportExportErrorCode::KisImportExportErrorCode() : errorFieldUsed(None), cannotRead(), cannotWrite() { } +KisImportExportErrorCode::KisImportExportErrorCode(ImportExportCodes::ErrorCodeID id) : errorFieldUsed(CodeId), codeId(id), cannotRead(), cannotWrite() { } +KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotRead error) : errorFieldUsed(CannotRead), cannotRead(error), cannotWrite() { } +KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotWrite error) : errorFieldUsed(CannotWrite), cannotRead(), cannotWrite(error) { } bool KisImportExportErrorCode::isOk() const { // if cannotRead or cannotWrite is "NoError", it means that something is wrong in our code return errorFieldUsed == CodeId && codeId == ImportExportCodes::OK; } bool KisImportExportErrorCode::isCancelled() const { return errorFieldUsed == CodeId && codeId == ImportExportCodes::Cancelled; } bool KisImportExportErrorCode::isInternalError() const { return errorFieldUsed == CodeId && codeId == ImportExportCodes::InternalError; } QString KisImportExportErrorCode::errorMessage() const { QString internal = i18n("Unexpected error. Please contact developers."); + QString unknown = i18n("Unknown error."); if (errorFieldUsed == CannotRead) { return cannotRead.errorMessage(); } else if (errorFieldUsed == CannotWrite) { return cannotWrite.errorMessage(); } else if (errorFieldUsed == CodeId) { switch (codeId) { // Reading case ImportExportCodes::FileNotExist: return i18n("The file doesn't exists."); case ImportExportCodes::NoAccessToRead: return i18n("Permission denied: Krita is not allowed to read the file."); case ImportExportCodes::FileFormatIncorrect: return i18n("The file format cannot be parsed."); case ImportExportCodes::FormatFeaturesUnsupported: return i18n("The file format contains unsupported features."); case ImportExportCodes::FormatColorSpaceUnsupported: return i18n("The file format contains unsupported color space."); + case ImportExportCodes::ErrorWhileReading: + return unknown; // Writing case ImportExportCodes::CannotCreateFile: return i18n("The file cannot be created."); case ImportExportCodes::NoAccessToWrite: return i18n("Permission denied: Krita is not allowed to write to the file."); case ImportExportCodes::InsufficientMemory: return i18n("There is not enough memory left to save the file."); + case ImportExportCodes::ErrorWhileWriting: + return unknown; // Both case ImportExportCodes::Cancelled: return i18n("The action was cancelled by the user."); // Other case ImportExportCodes::Failure: - return i18n("Unknown error."); + return unknown; case ImportExportCodes::InternalError: return internal; // OK case ImportExportCodes::OK: return i18n("The action has been completed successfully."); default: return internal; } } return internal; // errorFieldUsed = None } +bool KisImportExportErrorCode::operator==(KisImportExportErrorCode errorCode) +{ + if (errorFieldUsed != errorCode.errorFieldUsed) { + return false; + } + if (errorFieldUsed == CodeId) { + return codeId == errorCode.codeId; + } + if (errorFieldUsed == CannotRead) { + return cannotRead == errorCode.cannotRead; + } + return cannotWrite == errorCode.cannotWrite; +} + + QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode) { switch(errorCode.errorFieldUsed) { case KisImportExportErrorCode::None: d << "None of the error fields is in use."; break; case KisImportExportErrorCode::CannotRead: d << "Cannot read: " << errorCode.cannotRead.m_error; break; case KisImportExportErrorCode::CannotWrite: - d << "Cannot read: " << errorCode.cannotRead.m_error; + d << "Cannot write: " << errorCode.cannotRead.m_error; break; case KisImportExportErrorCode::CodeId: d << "Error code = " << errorCode.codeId; } d << " " << errorCode.errorMessage(); return d; } diff --git a/libs/ui/KisImportExportErrorCode.h b/libs/ui/KisImportExportErrorCode.h index bef2d6395a..7f9f5d0e02 100644 --- a/libs/ui/KisImportExportErrorCode.h +++ b/libs/ui/KisImportExportErrorCode.h @@ -1,141 +1,163 @@ /* * Copyright (c) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_IMPORT_EXPORT_ERROR_CODES_H #define KIS_IMPORT_EXPORT_ERROR_CODES_H #include #include #include #include #include namespace ImportExportCodes { enum KRITAUI_EXPORT ErrorCodeID { InternalError, // error that shouldn't happen; only inside ASSERTS // Reading FileNotExist, // there is no file with that name in that location, NoAccessToRead, // Krita has no reading access to the file, ErrorWhileReading, // there was an error that occured during reading, FileFormatIncorrect, // file format cannot be parsed, FormatFeaturesUnsupported, // file format can be parsed, but some features are unsupported, FormatColorSpaceUnsupported, // file format can be parsed, but color space of the image is unsupported // Writing CannotCreateFile, // file cannot be created NoAccessToWrite, // Krita has no writing access to the file ErrorWhileWriting, // there was an error that occured during writing (can be insufficient memory, too, just we don't know) InsufficientMemory, // there is not enough memory left // Both Cancelled, // cancelled by a user // Other Failure, // unspecified error // OK OK, // everything went ok }; }; struct KisImportExportErrorCode; struct KRITAUI_EXPORT KisImportExportComplexError { virtual QString errorMessage() const = 0; KisImportExportComplexError(QFileDevice::FileError error); friend QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode); + + protected: QString qtErrorMessage() const; QFileDevice::FileError m_error; virtual ~KisImportExportComplexError() {} }; struct KRITAUI_EXPORT KisImportExportErrorCannotWrite : KisImportExportComplexError { KisImportExportErrorCannotWrite(QFileDevice::FileError error); QString errorMessage() const override; ~KisImportExportErrorCannotWrite() { } + + bool operator==(KisImportExportErrorCannotWrite other); + +private: + KisImportExportErrorCannotWrite(); + + //friend KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotWrite code); + friend class KisImportExportErrorCode; + }; struct KRITAUI_EXPORT KisImportExportErrorCannotRead : KisImportExportComplexError { KisImportExportErrorCannotRead(QFileDevice::FileError error); QString errorMessage() const override; ~KisImportExportErrorCannotRead() { } + + bool operator==(KisImportExportErrorCannotRead other); + +private: + KisImportExportErrorCannotRead(); + + //friend KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotRead code); + friend class KisImportExportErrorCode; + }; struct KRITAUI_EXPORT KisImportExportErrorCode { public: // required by kis_async_action_feedback KisImportExportErrorCode(); KisImportExportErrorCode(ImportExportCodes::ErrorCodeID code); KisImportExportErrorCode(KisImportExportErrorCannotRead code); KisImportExportErrorCode(KisImportExportErrorCannotWrite code); QString errorMessage() const; bool isOk() const; bool isCancelled() const; bool isInternalError() const; friend QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode); + bool operator==(KisImportExportErrorCode errorCode); + private: enum ErrorFieldUsed { None, CodeId, CannotRead, CannotWrite }; ErrorFieldUsed errorFieldUsed; ImportExportCodes::ErrorCodeID codeId; KisImportExportErrorCannotRead cannotRead; KisImportExportErrorCannotWrite cannotWrite; }; KRITAUI_EXPORT QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode); #endif // KIS_IMPORT_EXPORT_ERROR_CODES_H diff --git a/libs/ui/KisImportExportFilter.h b/libs/ui/KisImportExportFilter.h index 8fa8b4d961..48282d157b 100644 --- a/libs/ui/KisImportExportFilter.h +++ b/libs/ui/KisImportExportFilter.h @@ -1,183 +1,185 @@ /* This file is part of the Calligra libraries Copyright (C) 2001 Werner Trobin 2002 Werner Trobin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_IMPORT_EXPORT_FILTER_H #define KIS_IMPORT_EXPORT_FILTER_H +#include "KisImageBuilderResult.h" + #include #include #include #include #include #include #include #include #include #include #include #include class KoUpdater; class KisDocument; class KisConfigWidget; #include "kritaui_export.h" - +#include "KisImportExportErrorCode.h" /** * @brief The base class for import and export filters. * * Derive your filter class from this base class and implement * the @ref convert() method. Don't forget to specify the Q_OBJECT * macro in your class even if you don't use signals or slots. * This is needed as filters are created on the fly. * * @note Take care: The m_chain pointer is invalid while the constructor * runs due to the implementation -- @em don't use it in the constructor. * After the constructor, when running the @ref convert() method it's * guaranteed to be valid, so no need to check against 0. * * @note If the code is compiled in debug mode, setting CALLIGRA_DEBUG_FILTERS * environment variable to any value disables deletion of temporary files while * importing/exporting. This is useful for testing purposes. * * @author Werner Trobin * @todo the class has no constructor and therefore cannot initialize its private class */ class KRITAUI_EXPORT KisImportExportFilter : public QObject { Q_OBJECT public: static const QString ImageContainsTransparencyTag; static const QString ColorModelIDTag; static const QString ColorDepthIDTag; static const QString sRGBTag; public: /** * This enum is used to signal the return state of your filter. * Return OK in @ref convert() in case everything worked as expected. * Feel free to add some more error conditions @em before the last item * if it's needed. */ enum ConversionStatus { OK, UsageError, CreationError, FileNotFound, StorageCreationError, BadMimeType, BadConversionGraph, WrongFormat, NotImplemented, ParsingError, InternalError, UserCancelled, InvalidFormat, FilterCreationError, ProgressCancelled, UnsupportedVersion, JustInCaseSomeBrokenCompilerUsesLessThanAByte = 255 }; ~KisImportExportFilter() override; void setBatchMode(bool batchmode); void setFilename(const QString &filename); void setRealFilename(const QString &filename); void setMimeType(const QString &mime); void setUpdater(QPointer updater); /** * The filter chain calls this method to perform the actual conversion. * The passed mimetypes should be a pair of those you specified in your * .desktop file. * You @em have to implement this method to make the filter work. * * @return The error status, see the @ref #ConversionStatus enum. * KisImportExportFilter::OK means that everything is alright. */ - virtual ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) = 0; + virtual KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) = 0; /** * Get the text version of the status value */ static QString conversionStatusString(ConversionStatus status); /** * @brief defaultConfiguration defines the default settings for the given import export filter * @param from The mimetype of the source file/document * @param to The mimetype of the destination file/document * @return a serializable KisPropertiesConfiguration object */ virtual KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const; /** * @brief lastSavedConfiguration return the last saved configuration for this filter * @param from The mimetype of the source file/document * @param to The mimetype of the destination file/document * @return a serializable KisPropertiesConfiguration object */ KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from = "", const QByteArray &to = "") const; /** * @brief createConfigurationWidget creates a widget that can be used to define the settings for a given import/export filter * @param parent the owner of the widget; the caller is responsible for deleting * @param from The mimetype of the source file/document * @param to The mimetype of the destination file/document * * @return the widget */ virtual KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const; /** * @brief generate and return the list of capabilities of this export filter. The list * @return returns the list of capabilities of this export filter */ virtual QMap exportChecks(); /// Override and return false for the filters that use a library that cannot handle file handles, only file names. virtual bool supportsIO() const { return true; } protected: /** * This is the constructor your filter has to call, obviously. */ KisImportExportFilter(QObject *parent = 0); QString filename() const; QString realFilename() const; bool batchMode() const; QByteArray mimeType() const; void setProgress(int value); virtual void initializeCapabilities(); void addCapability(KisExportCheckBase *capability); void addSupportedColorModels(QList > supportedColorModels, const QString &name, KisExportCheckBase::Level level = KisExportCheckBase::PARTIALLY); private: KisImportExportFilter(const KisImportExportFilter& rhs); KisImportExportFilter& operator=(const KisImportExportFilter& rhs); class Private; Private *const d; }; #endif diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp index e5ac96de10..33c5a40ea7 100644 --- a/libs/ui/KisImportExportManager.cpp +++ b/libs/ui/KisImportExportManager.cpp @@ -1,699 +1,687 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisImportExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "KisImportExportFilter.h" #include "KisDocument.h" #include #include #include "kis_painter.h" #include "kis_guides_config.h" #include "kis_grid_config.h" #include "kis_popup_button.h" #include #include "kis_async_action_feedback.h" #include "KisReferenceImagesLayer.h" // static cache for import and export mimetypes QStringList KisImportExportManager::m_importMimeTypes; QStringList KisImportExportManager::m_exportMimeTypes; class Q_DECL_HIDDEN KisImportExportManager::Private { public: KoUpdaterPtr updater; QString cachedExportFilterMimeType; QSharedPointer cachedExportFilter; }; struct KisImportExportManager::ConversionResult { ConversionResult() { } - ConversionResult(const QFuture &futureStatus) + ConversionResult(const QFuture &futureStatus) : m_isAsync(true), m_futureStatus(futureStatus) { } - ConversionResult(KisImportExportFilter::ConversionStatus status) + ConversionResult(KisImportExportErrorCode status) : m_isAsync(false), m_status(status) { } bool isAsync() const { return m_isAsync; } - QFuture futureStatus() const { + QFuture futureStatus() const { // if the result is not async, then it means some failure happened, // just return a cancelled future - KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || m_status != KisImportExportFilter::OK); + KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || !m_status.isOk()); return m_futureStatus; } - KisImportExportFilter::ConversionStatus status() const { + KisImportExportErrorCode status() const { return m_status; } - void setStatus(KisImportExportFilter::ConversionStatus value) { + void setStatus(KisImportExportErrorCode value) { m_status = value; } private: bool m_isAsync = false; - QFuture m_futureStatus; - KisImportExportFilter::ConversionStatus m_status = KisImportExportFilter::UsageError; + QFuture m_futureStatus; + KisImportExportErrorCode m_status = ImportExportCodes::InternalError; }; KisImportExportManager::KisImportExportManager(KisDocument* document) : m_document(document) , d(new Private) { } KisImportExportManager::~KisImportExportManager() { delete d; } -KisImportExportFilter::ConversionStatus KisImportExportManager::importDocument(const QString& location, const QString& mimeType) +KisImportExportErrorCode KisImportExportManager::importDocument(const QString& location, const QString& mimeType) { ConversionResult result = convert(Import, location, location, mimeType, false, 0, false); - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError); return result.status(); } -KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) +KisImportExportErrorCode KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false); - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError); return result.status(); } -QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, KisImportExportFilter::ConversionStatus &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) +QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, + KisImportExportErrorCode &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync() || - result.status() != KisImportExportFilter::OK, QFuture()); + !result.status().isOk(), QFuture()); status = result.status(); return result.futureStatus(); } // The static method to figure out to which parts of the // graph this mimetype has a connection to. QStringList KisImportExportManager::supportedMimeTypes(Direction direction) { // Find the right mimetype by the extension QSet mimeTypes; // mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster"; if (direction == KisImportExportManager::Import) { if (m_importMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_importMimeTypes = mimeTypes.toList(); } return m_importMimeTypes; } else if (direction == KisImportExportManager::Export) { if (m_exportMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_exportMimeTypes = mimeTypes.toList(); } return m_exportMimeTypes; } return QStringList(); } KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction) { int weight = -1; KisImportExportFilter *filter = 0; QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import"; if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) { KLibFactory *factory = qobject_cast(loader->instance()); if (!factory) { warnUI << loader->errorString(); continue; } QObject* obj = factory->create(0); if (!obj || !obj->inherits("KisImportExportFilter")) { delete obj; continue; } KisImportExportFilter *f = qobject_cast(obj); if (!f) { delete obj; continue; } int w = json.value("X-KDE-Weight").toInt(); if (w > weight) { delete filter; filter = f; f->setObjectName(loader->fileName()); weight = w; } } } qDeleteAll(list); if (filter) { filter->setMimeType(mimetype); } return filter; } bool KisImportExportManager::batchMode(void) const { return m_document->fileBatchMode(); } void KisImportExportManager::setUpdater(KoUpdaterPtr updater) { d->updater = updater; } QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent) { KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio"); if (!defaultDir.isEmpty()) { dialog.setDefaultDir(defaultDir); } QStringList mimeTypes; mimeTypes << "audio/mpeg"; mimeTypes << "audio/ogg"; mimeTypes << "audio/vorbis"; mimeTypes << "audio/vnd.wave"; mimeTypes << "audio/flac"; dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@titile:window", "Open Audio")); return dialog.filename(); } KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync) { // export configuration is supported for export only KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration)); QString typeName = mimeType; if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true); } QSharedPointer filter; /** * Fetching a filter from the registry is a really expensive operation, * because it blocks all the threads. Cache the filter if possible. */ if (direction == KisImportExportManager::Export && d->cachedExportFilter && d->cachedExportFilterMimeType == typeName) { filter = d->cachedExportFilter; } else { filter = toQShared(filterForMimeType(typeName, direction)); if (direction == Export) { d->cachedExportFilter = filter; d->cachedExportFilterMimeType = typeName; } } if (!filter) { - return KisImportExportFilter::FilterCreationError; + return KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect); } filter->setFilename(location); filter->setRealFilename(realLocation); filter->setBatchMode(batchMode()); filter->setMimeType(typeName); if (!d->updater.isNull()) { // WARNING: The updater is not guaranteed to be persistent! If you ever want // to add progress reporting to "Save also as .kra", make sure you create // a separate KoProgressUpdater for that! // WARNING2: the failsafe completion of the updater happens in the destructor // the filter. filter->setUpdater(d->updater); } QByteArray from, to; if (direction == Export) { from = m_document->nativeFormatMimeType(); to = typeName.toLatin1(); } else { from = typeName.toLatin1(); to = m_document->nativeFormatMimeType(); } KIS_ASSERT_RECOVER_RETURN_VALUE( direction == Import || direction == Export, - KisImportExportFilter::BadConversionGraph); + KisImportExportErrorCode(ImportExportCodes::InternalError)); // "bad conversion graph" - ConversionResult result = KisImportExportFilter::OK; + ConversionResult result = KisImportExportErrorCode(ImportExportCodes::OK); if (direction == Import) { KisUsageLogger::log(QString("Importing %1 to %2. Location: %3. Real location: %4. Batchmode: %5") .arg(QString::fromLatin1(from)) .arg(QString::fromLatin1(to)) .arg(location) .arg(realLocation) .arg(batchMode())); // async importing is not yet supported! KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync); if (0 && !batchMode()) { KisAsyncActionFeedback f(i18n("Opening document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter)); } else { result = doImport(location, filter); } } else /* if (direction == Export) */ { if (!exportConfiguration) { exportConfiguration = filter->lastSavedConfiguration(from, to); } if (exportConfiguration) { fillStaticExportConfigurationProperties(exportConfiguration); } bool alsoAsKra = false; bool askUser = askUserAboutExportConfiguration(filter, exportConfiguration, from, to, batchMode(), showWarnings, &alsoAsKra); if (!batchMode() && !askUser) { - return KisImportExportFilter::UserCancelled; + return KisImportExportErrorCode(ImportExportCodes::Cancelled); } KisUsageLogger::log(QString("Converting from %1 to %2. Location: %3. Real location: %4. Batchmode: %5. Configuration: %6") .arg(QString::fromLatin1(from)) .arg(QString::fromLatin1(to)) .arg(location) .arg(realLocation) .arg(batchMode()) .arg(exportConfiguration ? exportConfiguration->toXML() : "none")); if (isAsync) { result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); // we should explicitly report that the exporting has been initiated - result.setStatus(KisImportExportFilter::OK); + result.setStatus(ImportExportCodes::OK); } else if (!batchMode()) { KisAsyncActionFeedback f(i18n("Saving document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); } else { result = doExport(location, filter, exportConfiguration, alsoAsKra); } if (exportConfiguration && !batchMode() && showWarnings) { KisConfig(false).setExportConfiguration(typeName, exportConfiguration); } } return result; } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image) { KisPaintDeviceSP dev = image->projection(); const KoColorSpace* cs = dev->colorSpace(); const bool isThereAlpha = KisPainter::checkDeviceHasTransparency(image->projection()); exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha); exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id()); exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id()); const bool sRGB = (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) && !cs->profile()->name().contains(QLatin1String("g10"))); exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB); } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration) { return fillStaticExportConfigurationProperties(exportConfiguration, m_document->image()); } bool KisImportExportManager::askUserAboutExportConfiguration( QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, const bool batchMode, const bool showWarnings, bool *alsoAsKra) { // prevents the animation renderer from running this code const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to); QStringList warnings; QStringList errors; { KisPreExportChecker checker; checker.check(m_document->image(), filter->exportChecks()); warnings = checker.warnings(); errors = checker.errors(); } KisConfigWidget *wdg = 0; if (QThread::currentThread() == qApp->thread()) { wdg = filter->createConfigurationWidget(0, from, to); } // Extra checks that cannot be done by the checker, because the checker only has access to the image. if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains assistants. The assistants will not be saved.")); } if (m_document->referenceImagesLayer() && m_document->referenceImagesLayer()->shapeCount() > 0 && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains reference images. The reference images will not be saved.")); } if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains guides. The guides will not be saved.")); } if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains a custom grid configuration. The configuration will not be saved.")); } if (!batchMode && !errors.isEmpty()) { QString error = "

" + i18n("Error: cannot save this image as a %1.", mimeUserDescription) + " Reasons:

" + "

    "; Q_FOREACH(const QString &w, errors) { error += "\n
  • " + w + "
  • "; } error += "
"; QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error); return false; } if (!batchMode && (wdg || !warnings.isEmpty())) { KoDialog dlg; dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); dlg.setWindowTitle(mimeUserDescription); QWidget *page = new QWidget(&dlg); QVBoxLayout *layout = new QVBoxLayout(page); if (showWarnings && !warnings.isEmpty()) { QHBoxLayout *hLayout = new QHBoxLayout(); QLabel *labelWarning = new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hLayout->addWidget(labelWarning); KisPopupButton *bn = new KisPopupButton(0); bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as %1 will lose information from your image. ", mimeUserDescription)); hLayout->addWidget(bn); layout->addLayout(hLayout); QTextBrowser *browser = new QTextBrowser(); browser->setMinimumWidth(bn->width()); bn->setPopupWidget(browser); QString warning = "

" + i18n("You will lose information when saving this image as a %1.", mimeUserDescription); if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); } if (wdg) { QGroupBox *box = new QGroupBox(i18n("Options")); QVBoxLayout *boxLayout = new QVBoxLayout(box); wdg->setConfiguration(exportConfiguration); boxLayout->addWidget(wdg); layout->addWidget(box); } QCheckBox *chkAlsoAsKra = 0; if (showWarnings && !warnings.isEmpty()) { chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file.")); chkAlsoAsKra->setChecked(KisConfig(true).readEntry("AlsoSaveAsKra", false)); layout->addWidget(chkAlsoAsKra); } dlg.setMainWidget(page); dlg.resize(dlg.minimumSize()); if (showWarnings || wdg) { if (!dlg.exec()) { return false; } } *alsoAsKra = false; if (chkAlsoAsKra) { KisConfig(false).writeEntry("AlsoSaveAsKra", chkAlsoAsKra->isChecked()); *alsoAsKra = chkAlsoAsKra->isChecked(); } if (wdg) { *exportConfiguration = *wdg->configuration(); } } return true; } -KisImportExportFilter::ConversionStatus KisImportExportManager::doImport(const QString &location, QSharedPointer filter) +KisImportExportErrorCode KisImportExportManager::doImport(const QString &location, QSharedPointer filter) { QFile file(location); if (!file.exists()) { - return KisImportExportFilter::FileNotFound; + return ImportExportCodes::FileNotExist; } if (filter->supportsIO() && !file.open(QFile::ReadOnly)) { - return KisImportExportFilter::FileNotFound; + return KisImportExportErrorCode(KisImportExportErrorCannotRead(file.error())); } - KisImportExportFilter::ConversionStatus status = - filter->convert(m_document, &file, KisPropertiesConfigurationSP()); + KisImportExportErrorCode status = filter->convert(m_document, &file, KisPropertiesConfigurationSP()); if (file.isOpen()) { file.close(); } return status; } -KisImportExportFilter::ConversionStatus KisImportExportManager::doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra) +KisImportExportErrorCode KisImportExportManager::doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra) { - KisImportExportFilter::ConversionStatus status = + KisImportExportErrorCode status = doExportImpl(location, filter, exportConfiguration); - if (alsoAsKra && status == KisImportExportFilter::OK) { + if (alsoAsKra && status.isOk()) { QString kraLocation = location + ".kra"; QByteArray mime = m_document->nativeFormatMimeType(); QSharedPointer filter( filterForMimeType(QString::fromLatin1(mime), Export)); KIS_SAFE_ASSERT_RECOVER_NOOP(filter); if (filter) { filter->setFilename(kraLocation); KisPropertiesConfigurationSP kraExportConfiguration = filter->lastSavedConfiguration(mime, mime); status = doExportImpl(kraLocation, filter, kraExportConfiguration); } else { - status = KisImportExportFilter::FilterCreationError; + status = ImportExportCodes::FileFormatIncorrect; } } return status; } // Temporary workaround until QTBUG-57299 is fixed. #ifndef Q_OS_WIN #define USE_QSAVEFILE #endif -KisImportExportFilter::ConversionStatus KisImportExportManager::doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration) +KisImportExportErrorCode KisImportExportManager::doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration) { #ifdef USE_QSAVEFILE QSaveFile file(location); file.setDirectWriteFallback(true); if (filter->supportsIO() && !file.open(QFile::WriteOnly)) { #else QFileInfo fi(location); QTemporaryFile file(fi.absolutePath() + ".XXXXXX.kra"); if (filter->supportsIO() && !file.open()) { #endif - QString error = file.errorString(); - if (error.isEmpty()) { - error = i18n("Could not open %1 for writing.", location); - } - m_document->setErrorMessage(error); + KisImportExportErrorCannotWrite result(file.error()); #ifdef USE_QSAVEFILE file.cancelWriting(); #endif - return KisImportExportFilter::CreationError; + return result; } - KisImportExportFilter::ConversionStatus status = filter->convert(m_document, &file, exportConfiguration); + KisImportExportErrorCode status = filter->convert(m_document, &file, exportConfiguration); if (filter->supportsIO()) { - if (status != KisImportExportFilter::OK) { + if (!status.isOk()) { #ifdef USE_QSAVEFILE file.cancelWriting(); #endif } else { #ifdef USE_QSAVEFILE if (!file.commit()) { qWarning() << "Could not commit QSaveFile"; - QString error = file.errorString(); - if (error.isEmpty()) { - error = i18n("Could not write to %1.", location); - } - if (m_document->errorMessage().isEmpty()) { - m_document->setErrorMessage(error); - } - status = KisImportExportFilter::CreationError; + status = KisImportExportErrorCannotWrite(file.error()); } #else file.flush(); file.close(); QFile target(location); if (target.exists()) { // There should already be a .kra~ backup target.remove(); } if (!file.copy(location)) { file.setAutoRemove(false); - m_document->setErrorMessage(i18n("Could not copy %1 to its final location %2", file.fileName(), location)); - return KisImportExportFilter::CreationError; + return KisImportExportErrorCannotWrite(file.error()); } #endif } } return status; } #include diff --git a/libs/ui/KisImportExportManager.h b/libs/ui/KisImportExportManager.h index 9e8785608b..4a6a358736 100644 --- a/libs/ui/KisImportExportManager.h +++ b/libs/ui/KisImportExportManager.h @@ -1,158 +1,158 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_IMPORT_EXPORT_MANAGER_H #define KIS_IMPORT_EXPORT_MANAGER_H #include #include #include #include #include "KisImportExportFilter.h" #include "kritaui_export.h" class KisDocument; class KoProgressUpdater; template class QFuture; /** * @brief The class managing all the filters. * * This class manages all filters for a %Calligra application. Normally * you will not have to use it, since KisMainWindow takes care of loading * and saving documents. * * @ref KisFilter * * @author Kalle Dalheimer * @author Torben Weis * @author Werner Trobin */ class KRITAUI_EXPORT KisImportExportManager : public QObject { Q_OBJECT public: /** * This enum is used to distinguish the import/export cases */ enum Direction { Import = 1, Export = 2 }; /** * Create a filter manager for a document */ explicit KisImportExportManager(KisDocument *document); public: ~KisImportExportManager() override; /** * Imports the specified document and returns the resultant filename * (most likely some file in /tmp). * @p path can be either a URL or a filename. * @p documentMimeType gives importDocument a hint about what type * the document may be. It can be left empty. * * @return status signals the success/error of the conversion. * If the QString which is returned isEmpty() and the status is OK, * then we imported the file directly into the document. */ - KisImportExportFilter::ConversionStatus importDocument(const QString &location, const QString &mimeType); + KisImportExportErrorCode importDocument(const QString &location, const QString &mimeType); /** * @brief Exports the given file/document to the specified URL/mimetype. * * If @p mimeType is empty, then the closest matching Calligra part is searched * and when the method returns @p mimeType contains this mimetype. * Oh, well, export is a C++ keyword ;) */ - KisImportExportFilter::ConversionStatus exportDocument(const QString &location, const QString& realLocation, const QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); + KisImportExportErrorCode exportDocument(const QString &location, const QString& realLocation, const QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); - QFuture exportDocumentAsyc(const QString &location, const QString& realLocation, const QByteArray &mimeType, KisImportExportFilter::ConversionStatus &status, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); + QFuture exportDocumentAsyc(const QString &location, const QString& realLocation, const QByteArray &mimeType, KisImportExportErrorCode &status, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); ///@name Static API //@{ /** * Suitable for passing to KoFileDialog::setMimeTypeFilters. The default mime * gets set by the "users" of this method, as we do not have enough * information here. * Optionally, @p extraNativeMimeTypes are added after the native mimetype. */ static QStringList supportedMimeTypes(Direction direction); /** * @brief filterForMimeType loads the relevant import/export plugin and returns it. The caller * is responsible for deleting it! * @param mimetype the mimetype we want to import/export. If there's more than one plugin, the one * with the highest weight as defined in the json description will be taken * @param direction import or export * @return a pointer to the filter plugin or 0 if none could be found */ static KisImportExportFilter *filterForMimeType(const QString &mimetype, Direction direction); /** * Fill necessary information for the export filter into the properties, e.g. if the image has * transparency or has sRGB profile. */ static void fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image); /** * Get if the filter manager is batch mode (true) * or in interactive mode (true) */ bool batchMode(void) const; void setUpdater(KoUpdaterPtr updater); static QString askForAudioFileName(const QString &defaultDir, QWidget *parent); private: struct ConversionResult; ConversionResult convert(Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync); void fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration); bool askUserAboutExportConfiguration(QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, bool batchMode, const bool showWarnings, bool *alsoAsKra); - KisImportExportFilter::ConversionStatus doImport(const QString &location, QSharedPointer filter); + KisImportExportErrorCode doImport(const QString &location, QSharedPointer filter); - KisImportExportFilter::ConversionStatus doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra); - KisImportExportFilter::ConversionStatus doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration); + KisImportExportErrorCode doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra); + KisImportExportErrorCode doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration); // Private API KisImportExportManager(const KisImportExportManager& rhs); KisImportExportManager &operator=(const KisImportExportManager& rhs); KisDocument *m_document; /// A static cache for the availability checks of filters static QStringList m_importMimeTypes; static QStringList m_exportMimeTypes; class Private; Private * const d; }; #endif // __KO_FILTER_MANAGER_H__ diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index a0e42f34f2..a4ecc63e48 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2652 +1,2652 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #include #include class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { if (id.isNull()) this->id = QUuid::createUuid(); widgetStack->addWidget(welcomePage); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisSignalAutoConnectionsStore screenConnectionsStore; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } KToolBar::setToolBarsLocked(KisConfig(true).readEntry("LockAllDockerPanels", false)); plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } if (cfg.readEntry("CanvasOnlyActive", false)) { QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(currentWorkspace); if (workspace) { restoreWorkspace(workspace); } cfg.writeEntry("CanvasOnlyActive", false); menuBar()->setVisible(true); } this->winId(); // Ensures the native window has been created. QWindow *window = this->windowHandle(); connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *))); } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // qDebug() << "", "").replace("", "") // << "\n\ticonText=" << action->iconText().replace("&", "&") // << "\n\tshortcut=" << action->shortcut().toString() // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "\n\tstatusTip=" << action->statusTip() // << "\n/>\n" ; // } // else { // dbgKrita << "Got a non-qaction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { /** * If we are the last view of the window, Qt will not activate another tab * before destroying tab/window. In ths case we should clear oll the dangling * pointers manually by setting the current view to null */ viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView); imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } updateWindowMenu(); d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } if (d->mdiArea) { d->mdiArea->setPalette(qApp->palette()); for (int i=0; imdiArea->subWindowList().size(); i++) { QMdiSubWindow *window = d->mdiArea->subWindowList().at(i); if (window) { window->setPalette(qApp->palette()); KisView *view = qobject_cast(window->widget()); if (view) { view->slotThemeChanged(qApp->palette()); } } } } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } const QStringList templateDirs = KoResourcePaths::findDirs("templates"); for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the templates directory. break; } } } if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) { d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); } else { d->saveAction->setToolTip(i18n("Save")); } } } void KisMainWindow::updateCaption(const QString &caption, bool modified) { QString versionString = KritaVersionWrapper::versionString(true); QString title = caption; if (!title.contains(QStringLiteral("[*]"))) { // append the placeholder so that the modified mechanism works title.append(QStringLiteral(" [*]")); } if (d->mdiArea->activeSubWindow()) { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) d->mdiArea->activeSubWindow()->setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else d->mdiArea->activeSubWindow()->setWindowTitle(title); #endif d->mdiArea->activeSubWindow()->setWindowModified(modified); } else { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else setWindowTitle(title); #endif } setWindowModified(modified); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } if (document->url().isEmpty()) { saveas = true; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).baseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty()) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.baseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).baseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).baseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; + } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; } } updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragMove(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeave() { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceProvider *KisMainWindow::resourceManager() const { return d->viewManager->canvasResourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace) { bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); - KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); - - if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) { - QString msg = KisImportExportFilter::conversionStatusString(status); + KisImportExportErrorCode status = importer.import(files, firstFrame, step); + if (!status.isOk() && !status.isInternalError()) { + QString msg = status.errorMessage(); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_MACOS dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } /** * Qt has a weirdness, it has hardcoded shortcuts added to an action * in the window menu. We need to reset the shortcuts for that menu * to nothing, otherwise the shortcuts cannot be made configurable. * * See: https://bugs.kde.org/show_bug.cgi?id=352205 * https://bugs.kde.org/show_bug.cgi?id=375524 * https://bugs.kde.org/show_bug.cgi?id=398729 */ QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); if (subWindow) { QMenu *menu = subWindow->systemMenu(); if (menu && menu->actions().size() == 8) { Q_FOREACH (QAction *action, menu->actions()) { action->setShortcut(QKeySequence()); } menu->actions().last()->deleteLater(); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ if (viewManager() && viewManager()->selectionManager()) { viewManager()->selectionManager()->selectionChanged(); } KisPart *kisPart = KisPart::instance(); KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); QFontMetrics fontMetrics = docMenu->fontMetrics(); int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(w); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QString extensions = d->workspacemodel->extensions(); QStringList mimeTypes; for(const QString &suffix : extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(""); workspace->setDockerState(m_this->saveState()); d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } else { text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will contiue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QString s = cfg.getMDIBackgroundColor(); KoColor c = KoColor::fromXML(s); QBrush brush(c.toQColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { if (d->mdiArea->currentSubWindow()) { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_MACOS w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.baseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to compensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::windowScreenChanged(QScreen *screen) { emit screenChanged(); d->screenConnectionsStore.clear(); d->screenConnectionsStore.addConnection(screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SIGNAL(screenChanged())); } #include diff --git a/libs/ui/KisMouseClickEater.cpp b/libs/ui/KisMouseClickEater.cpp new file mode 100644 index 0000000000..8641a0bb60 --- /dev/null +++ b/libs/ui/KisMouseClickEater.cpp @@ -0,0 +1,70 @@ +#include "KisMouseClickEater.h" + +#include +#include "kis_debug.h" + +KisMouseClickEater::KisMouseClickEater(Qt::MouseButtons buttons, + int clicksToEat, + QObject *parent) + : QObject(parent), + m_buttons(buttons), + m_clicksToEat(clicksToEat) +{ + reset(); +} + +KisMouseClickEater::~KisMouseClickEater() +{ +} + +void KisMouseClickEater::reset() +{ + m_clicksHappened = 0; + m_timeSinceReset.start(); + m_blockTimedRelease = false; +} + +bool KisMouseClickEater::eventFilter(QObject *watched, QEvent *event) +{ +#ifdef Q_OS_WIN + const int tabletMouseEventsFlowDelay = 500; +#else + const int tabletMouseEventsFlowDelay = 100; +#endif + + if (event->type() == QEvent::TabletMove) { + m_blockTimedRelease = true; + } + + if (!m_blockTimedRelease && + m_timeSinceReset.elapsed() > tabletMouseEventsFlowDelay) { + + return QObject::eventFilter(watched, event); + } + + if (event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::MouseButtonRelease) { + + QMouseEvent *mevent = static_cast(event); + if (mevent->button() & m_buttons) { + if (m_clicksHappened >= m_clicksToEat) { + return false; + } + + if (event->type() == QEvent::MouseButtonRelease) { + m_clicksHappened++; + } + + return true; + } + } + + if (event->type() == QEvent::MouseMove) { + QMouseEvent *mevent = static_cast(event); + if (mevent->buttons() & m_buttons) { + return m_clicksHappened < m_clicksToEat; + } + } + + return QObject::eventFilter(watched, event); +} diff --git a/libs/ui/KisMouseClickEater.h b/libs/ui/KisMouseClickEater.h new file mode 100644 index 0000000000..eaa470aaab --- /dev/null +++ b/libs/ui/KisMouseClickEater.h @@ -0,0 +1,29 @@ +#ifndef KISMOUSECLICKEATER_H +#define KISMOUSECLICKEATER_H + +#include +#include +#include + + +class KisMouseClickEater : public QObject +{ +public: + KisMouseClickEater(Qt::MouseButtons buttons, + int clicksToEat = 1, + QObject *parent = 0); + + ~KisMouseClickEater(); + + void reset(); + bool eventFilter(QObject *watched, QEvent *event) override; + +private: + Qt::MouseButtons m_buttons = Qt::NoButton; + int m_clicksToEat = 1; + int m_clicksHappened = 0; + bool m_blockTimedRelease = false; + QElapsedTimer m_timeSinceReset; +}; + +#endif // KISMOUSECLICKEATER_H diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp index 3bf44fcbdf..f246978d85 100644 --- a/libs/ui/canvas/kis_canvas2.cpp +++ b/libs/ui/canvas/kis_canvas2.cpp @@ -1,1274 +1,1293 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) Lukáš Tvrdý , (C) 2010 * Copyright (C) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.. */ #include "kis_canvas2.h" #include #include #include #include #include #include #include #include #include +#include +#include #include #include #include #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include "kis_coordinates_converter.h" #include "kis_prescaled_projection.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_undo_adapter.h" #include "flake/kis_shape_layer.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_abstract_canvas_widget.h" #include "kis_qpainter_canvas.h" #include "kis_group_layer.h" #include "flake/kis_shape_controller.h" #include "kis_node_manager.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "flake/kis_shape_selection.h" #include "kis_selection_mask.h" #include "kis_image_config.h" #include "kis_infinity_manager.h" #include "kis_signal_compressor.h" #include "kis_display_color_converter.h" #include "kis_exposure_gamma_correction_interface.h" #include "KisView.h" #include "kis_canvas_controller.h" #include "kis_grid_config.h" #include "kis_animation_player.h" #include "kis_animation_frame_cache.h" #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl.h" #include "kis_fps_decoration.h" #include "KoColorConversionTransformation.h" #include "KisProofingConfiguration.h" #include #include #include "input/kis_input_manager.h" #include "kis_painting_assistants_decoration.h" #include "kis_canvas_updates_compressor.h" #include "KoZoomController.h" #include #include "opengl/kis_opengl_canvas_debugger.h" #include "kis_algebra_2d.h" #include "kis_image_signal_router.h" #include "KisSnapPixelStrategy.h" class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private { public: KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer view, KoCanvasResourceProvider* resourceManager) : coordinatesConverter(coordConverter) , view(view) , shapeManager(parent) , selectedShapesProxy(&shapeManager) , toolProxy(parent) , displayColorConverter(resourceManager, view) , regionOfInterestUpdateCompressor(100, KisSignalCompressor::FIRST_INACTIVE) { } KisCoordinatesConverter *coordinatesConverter; QPointerview; KisAbstractCanvasWidget *canvasWidget = 0; KoShapeManager shapeManager; KisSelectedShapesProxy selectedShapesProxy; bool currentCanvasIsOpenGL; int openGLFilterMode; KisToolProxy toolProxy; KisPrescaledProjectionSP prescaledProjection; bool vastScrolling; KisSignalCompressor canvasUpdateCompressor; QRect savedUpdateRect; QBitArray channelFlags; KisProofingConfigurationSP proofingConfig; bool softProofing = false; bool gamutCheck = false; bool proofingConfigUpdated = false; KisPopupPalette *popupPalette = 0; KisDisplayColorConverter displayColorConverter; KisCanvasUpdatesCompressor projectionUpdatesCompressor; KisAnimationPlayer *animationPlayer; KisAnimationFrameCacheSP frameCache; bool lodAllowedInImage = false; bool bootstrapLodBlocked; QPointer currentlyActiveShapeManager; KisInputActionGroupsMask inputActionGroupsMask = AllActionGroup; KisSignalCompressor frameRenderStartCompressor; KisSignalCompressor regionOfInterestUpdateCompressor; QRect regionOfInterest; QRect renderingLimit; int isBatchUpdateActive = 0; bool effectiveLodAllowedInImage() { return lodAllowedInImage && !bootstrapLodBlocked; } void setActiveShapeManager(KoShapeManager *shapeManager); }; namespace { KoShapeManager* fetchShapeManagerFromNode(KisNodeSP node) { KoShapeManager *shapeManager = 0; KisSelectionSP selection; if (KisLayer *layer = dynamic_cast(node.data())) { KisShapeLayer *shapeLayer = dynamic_cast(layer); if (shapeLayer) { shapeManager = shapeLayer->shapeManager(); } } else if (KisSelectionMask *mask = dynamic_cast(node.data())) { selection = mask->selection(); } if (!shapeManager && selection && selection->hasShapeSelection()) { KisShapeSelection *shapeSelection = dynamic_cast(selection->shapeSelection()); KIS_ASSERT_RECOVER_RETURN_VALUE(shapeSelection, 0); shapeManager = shapeSelection->shapeManager(); } return shapeManager; } } KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceProvider *resourceManager, KisView *view, KoShapeControllerBase *sc) : KoCanvasBase(sc, resourceManager) , m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager)) { /** * While loading LoD should be blocked. Only when GUI has finished * loading and zoom level settled down, LoD is given a green * light. */ m_d->bootstrapLodBlocked = true; connect(view->mainWindow(), SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished())); connect(view->mainWindow(), SIGNAL(screenChanged()), SLOT(slotConfigChanged())); KisImageConfig config(false); m_d->canvasUpdateCompressor.setDelay(1000 / config.fpsLimit()); m_d->canvasUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); m_d->frameRenderStartCompressor.setDelay(1000 / config.fpsLimit()); m_d->frameRenderStartCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); snapGuide()->overrideSnapStrategy(KoSnapGuide::PixelSnapping, new KisSnapPixelStrategy()); } void KisCanvas2::setup() { // a bit of duplication from slotConfigChanged() KisConfig cfg(true); m_d->vastScrolling = cfg.vastScrolling(); m_d->lodAllowedInImage = cfg.levelOfDetailEnabled(); createCanvas(cfg.useOpenGL()); setLodAllowedInCanvas(m_d->lodAllowedInImage); m_d->animationPlayer = new KisAnimationPlayer(this); connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); /** * We switch the shape manager every time vector layer or * shape selection is activated. Flake does not expect this * and connects all the signals of the global shape manager * to the clients in the constructor. To workaround this we * forward the signals of local shape managers stored in the * vector layers to the signals of global shape manager. So the * sequence of signal deliveries is the following: * * shapeLayer.m_d.canvas.m_shapeManager.selection() -> * shapeLayer -> * shapeController -> * globalShapeManager.selection() */ KisShapeController *kritaShapeController = static_cast(shapeController()->documentBase()); connect(kritaShapeController, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); connect(kritaShapeController, SIGNAL(selectionContentChanged()), selectedShapesProxy(), SIGNAL(selectionContentChanged())); connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)), selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(&m_d->canvasUpdateCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate())); connect(this, SIGNAL(sigCanvasCacheUpdated()), &m_d->frameRenderStartCompressor, SLOT(start())); connect(&m_d->frameRenderStartCompressor, SIGNAL(timeout()), SLOT(updateCanvasProjection())); connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32))); connect(&m_d->regionOfInterestUpdateCompressor, SIGNAL(timeout()), SLOT(slotUpdateRegionOfInterest())); connect(m_d->view->document(), SIGNAL(sigReferenceImagesChanged()), this, SLOT(slotReferenceImagesChanged())); initializeFpsDecoration(); } void KisCanvas2::initializeFpsDecoration() { KisConfig cfg(true); const bool shouldShowDebugOverlay = (canvasIsOpenGL() && cfg.enableOpenGLFramerateLogging()) || cfg.enableBrushSpeedLogging(); if (shouldShowDebugOverlay && !decoration(KisFpsDecoration::idTag)) { addDecoration(new KisFpsDecoration(imageView())); if (cfg.enableBrushSpeedLogging()) { connect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas())); } } else if (!shouldShowDebugOverlay && decoration(KisFpsDecoration::idTag)) { m_d->canvasWidget->removeDecoration(KisFpsDecoration::idTag); disconnect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas())); } } KisCanvas2::~KisCanvas2() { if (m_d->animationPlayer->isPlaying()) { m_d->animationPlayer->forcedStopOnExit(); } delete m_d; } void KisCanvas2::setCanvasWidget(KisAbstractCanvasWidget *widget) { if (m_d->popupPalette) { m_d->popupPalette->setParent(widget->widget()); } if (m_d->canvasWidget != 0) { widget->setDecorations(m_d->canvasWidget->decorations()); // Redundant check for the constructor case, see below if(viewManager() != 0) viewManager()->inputManager()->removeTrackedCanvas(this); } m_d->canvasWidget = widget; // Either tmp was null or we are being called by KisCanvas2 constructor that is called by KisView // constructor, so the view manager still doesn't exists. if(m_d->canvasWidget != 0 && viewManager() != 0) viewManager()->inputManager()->addTrackedCanvas(this); if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) { KisInfinityManager *manager = new KisInfinityManager(m_d->view, this); manager->setVisible(true); m_d->canvasWidget->addDecoration(manager); } widget->widget()->setAutoFillBackground(false); widget->widget()->setAttribute(Qt::WA_OpaquePaintEvent); widget->widget()->setMouseTracking(true); widget->widget()->setAcceptDrops(true); KoCanvasControllerWidget *controller = dynamic_cast(canvasController()); if (controller && controller->canvas() == this) { controller->changeCanvasWidget(widget->widget()); } } bool KisCanvas2::canvasIsOpenGL() const { return m_d->currentCanvasIsOpenGL; } KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const { return KisOpenGL::FilterMode(m_d->openGLFilterMode); } void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const { QTransform transform = coordinatesConverter()->imageToDocumentTransform(); const QPoint intSpacing = m_d->view->document()->gridConfig().spacing(); const QPoint intOffset = m_d->view->document()->gridConfig().offset(); QPointF size = transform.map(QPointF(intSpacing)); spacing->rwidth() = size.x(); spacing->rheight() = size.y(); *offset = transform.map(QPointF(intOffset)); } bool KisCanvas2::snapToGrid() const { return m_d->view->document()->gridConfig().snapToGrid(); } qreal KisCanvas2::rotationAngle() const { return m_d->coordinatesConverter->rotationAngle(); } bool KisCanvas2::xAxisMirrored() const { return m_d->coordinatesConverter->xAxisMirrored(); } bool KisCanvas2::yAxisMirrored() const { return m_d->coordinatesConverter->yAxisMirrored(); } void KisCanvas2::channelSelectionChanged() { KisImageSP image = this->image(); m_d->channelFlags = image->rootLayer()->channelFlags(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); image->barrierLock(); m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags); startUpdateInPatches(image->bounds()); image->unlock(); } void KisCanvas2::addCommand(KUndo2Command *command) { // This method exists to support flake-related operations m_d->view->document()->addCommand(command); } void KisCanvas2::KisCanvas2Private::setActiveShapeManager(KoShapeManager *shapeManager) { if (shapeManager != currentlyActiveShapeManager) { currentlyActiveShapeManager = shapeManager; selectedShapesProxy.setShapeManager(shapeManager); } } KoShapeManager* KisCanvas2::shapeManager() const { KoShapeManager *localShapeManager = this->localShapeManager(); // sanity check for consistency of the local shape manager KIS_SAFE_ASSERT_RECOVER (localShapeManager == m_d->currentlyActiveShapeManager) { localShapeManager = globalShapeManager(); } return localShapeManager ? localShapeManager : globalShapeManager(); } KoSelectedShapesProxy* KisCanvas2::selectedShapesProxy() const { return &m_d->selectedShapesProxy; } KoShapeManager* KisCanvas2::globalShapeManager() const { return &m_d->shapeManager; } KoShapeManager *KisCanvas2::localShapeManager() const { KisNodeSP node = m_d->view->currentNode(); KoShapeManager *localShapeManager = fetchShapeManagerFromNode(node); if (localShapeManager != m_d->currentlyActiveShapeManager) { m_d->setActiveShapeManager(localShapeManager); } return localShapeManager; } void KisCanvas2::updateInputMethodInfo() { // TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget... } const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const { return m_d->coordinatesConverter; } KoViewConverter* KisCanvas2::viewConverter() const { return m_d->coordinatesConverter; } KisInputManager* KisCanvas2::globalInputManager() const { return m_d->view->globalInputManager(); } KisInputActionGroupsMask KisCanvas2::inputActionGroupsMask() const { return m_d->inputActionGroupsMask; } void KisCanvas2::setInputActionGroupsMask(KisInputActionGroupsMask mask) { m_d->inputActionGroupsMask = mask; } QWidget* KisCanvas2::canvasWidget() { return m_d->canvasWidget->widget(); } const QWidget* KisCanvas2::canvasWidget() const { return m_d->canvasWidget->widget(); } KoUnit KisCanvas2::unit() const { KoUnit unit(KoUnit::Pixel); KisImageWSP image = m_d->view->image(); if (image) { if (!qFuzzyCompare(image->xRes(), image->yRes())) { warnKrita << "WARNING: resolution of the image is anisotropic" << ppVar(image->xRes()) << ppVar(image->yRes()); } const qreal resolution = image->xRes(); unit.setFactor(resolution); } return unit; } KoToolProxy * KisCanvas2::toolProxy() const { return &m_d->toolProxy; } void KisCanvas2::createQPainterCanvas() { m_d->currentCanvasIsOpenGL = false; KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view); m_d->prescaledProjection = new KisPrescaledProjection(); m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter); m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(), m_d->displayColorConverter.renderingIntent(), m_d->displayColorConverter.conversionFlags()); m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter()); canvasWidget->setPrescaledProjection(m_d->prescaledProjection); setCanvasWidget(canvasWidget); } void KisCanvas2::createOpenGLCanvas() { KisConfig cfg(true); m_d->openGLFilterMode = cfg.openGLFilteringMode(); m_d->currentCanvasIsOpenGL = true; KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter); m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures()); setCanvasWidget(canvasWidget); } void KisCanvas2::createCanvas(bool useOpenGL) { // deinitialize previous canvas structures m_d->prescaledProjection = 0; m_d->frameCache = 0; KisConfig cfg(true); QDesktopWidget dw; const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView())); m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL && KisOpenGL::hasOpenGL()); m_d->displayColorConverter.setMonitorProfile(profile); if (useOpenGL && !KisOpenGL::hasOpenGL()) { warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n"; useOpenGL = false; } m_d->displayColorConverter.notifyOpenGLCanvasIsActive(useOpenGL); if (useOpenGL) { createOpenGLCanvas(); if (cfg.canvasState() == "OPENGL_FAILED") { // Creating the opengl canvas failed, fall back warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter."; m_d->displayColorConverter.notifyOpenGLCanvasIsActive(false); createQPainterCanvas(); } } else { createQPainterCanvas(); } if (m_d->popupPalette) { m_d->popupPalette->setParent(m_d->canvasWidget->widget()); } } void KisCanvas2::initializeImage() { KisImageSP image = m_d->view->image(); m_d->displayColorConverter.setImageColorSpace(image->colorSpace()); m_d->coordinatesConverter->setImage(image); m_d->toolProxy.initializeImage(image); connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateStarted()), SLOT(slotBeginUpdatesBatch()), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigNotifyBatchUpdateEnded()), SLOT(slotEndUpdatesBatch()), Qt::DirectConnection); connect(image->signalRouter(), SIGNAL(sigRequestLodPlanesSyncBlocked(bool)), SLOT(slotSetLodUpdatesBlocked(bool)), Qt::DirectConnection); connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig())); connect(image, SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(startResizingImage()), Qt::DirectConnection); connect(image->undoAdapter(), SIGNAL(selectionChanged()), SLOT(slotTrySwitchShapeManager())); connect(image, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), SLOT(slotImageColorSpaceChanged())); connect(image, SIGNAL(sigProfileChanged(const KoColorProfile*)), SLOT(slotImageColorSpaceChanged())); connectCurrentCanvas(); } void KisCanvas2::connectCurrentCanvas() { KisImageWSP image = m_d->view->image(); if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->setImage(image); } startResizingImage(); setLodAllowedInCanvas(m_d->lodAllowedInImage); emit sigCanvasEngineChanged(); } void KisCanvas2::resetCanvas(bool useOpenGL) { // we cannot reset the canvas before it's created, but this method might be called, // for instance when setting the monitor profile. if (!m_d->canvasWidget) { return; } KisConfig cfg(true); bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) || (m_d->currentCanvasIsOpenGL && m_d->openGLFilterMode != cfg.openGLFilteringMode()); if (needReset) { createCanvas(useOpenGL); connectCurrentCanvas(); notifyZoomChanged(); } updateCanvasWidgetImpl(); } void KisCanvas2::startUpdateInPatches(const QRect &imageRect) { /** * We don't do patched loading for openGL canvas, becasue it loads * the tiles, which are bascially "patches". Therefore, big chunks * of memory are never allocated. */ if (m_d->currentCanvasIsOpenGL) { startUpdateCanvasProjection(imageRect); } else { KisImageConfig imageConfig(true); int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < imageRect.height(); y += patchHeight) { for (int x = 0; x < imageRect.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); startUpdateCanvasProjection(patchRect); } } } } void KisCanvas2::setDisplayFilter(QSharedPointer displayFilter) { m_d->displayColorConverter.setDisplayFilter(displayFilter); KisImageSP image = this->image(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); image->barrierLock(); m_d->canvasWidget->setDisplayFilter(displayFilter); image->unlock(); } QSharedPointer KisCanvas2::displayFilter() const { return m_d->displayColorConverter.displayFilter(); } void KisCanvas2::slotImageColorSpaceChanged() { KisImageSP image = this->image(); m_d->view->viewManager()->blockUntilOperationsFinishedForced(image); m_d->displayColorConverter.setImageColorSpace(image->colorSpace()); image->barrierLock(); m_d->canvasWidget->notifyImageColorSpaceChanged(image->colorSpace()); image->unlock(); } KisDisplayColorConverter* KisCanvas2::displayColorConverter() const { return &m_d->displayColorConverter; } KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const { QSharedPointer displayFilter = m_d->displayColorConverter.displayFilter(); return displayFilter ? displayFilter->correctionInterface() : KisDumbExposureGammaCorrectionInterface::instance(); } void KisCanvas2::setProofingOptions(bool softProof, bool gamutCheck) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { KisImageConfig cfg(false); m_d->proofingConfig = cfg.defaultProofingconfiguration(); } KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags; #if QT_VERSION >= 0x050700 if (this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags.setFlag(KoColorConversionTransformation::SoftProofing, softProof); if (softProof) { conversionFlags.setFlag(KoColorConversionTransformation::GamutCheck, gamutCheck); } } #else if (this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags |= KoColorConversionTransformation::SoftProofing; } else { conversionFlags = conversionFlags & ~KoColorConversionTransformation::SoftProofing; } if (gamutCheck && softProof && this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags |= KoColorConversionTransformation::GamutCheck; } else { conversionFlags = conversionFlags & ~KoColorConversionTransformation::GamutCheck; } #endif m_d->proofingConfig->conversionFlags = conversionFlags; m_d->proofingConfigUpdated = true; startUpdateInPatches(this->image()->bounds()); } void KisCanvas2::slotSoftProofing(bool softProofing) { m_d->softProofing = softProofing; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotGamutCheck(bool gamutCheck) { m_d->gamutCheck = gamutCheck; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotChangeProofingConfig() { setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::setProofingConfigUpdated(bool updated) { m_d->proofingConfigUpdated = updated; } bool KisCanvas2::proofingConfigUpdated() { return m_d->proofingConfigUpdated; } KisProofingConfigurationSP KisCanvas2::proofingConfiguration() const { if (!m_d->proofingConfig) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { m_d->proofingConfig = KisImageConfig(true).defaultProofingconfiguration(); } } return m_d->proofingConfig; } void KisCanvas2::startResizingImage() { KisImageWSP image = this->image(); qint32 w = image->width(); qint32 h = image->height(); emit sigContinueResizeImage(w, h); QRect imageBounds(0, 0, w, h); startUpdateInPatches(imageBounds); } void KisCanvas2::finishResizingImage(qint32 w, qint32 h) { m_d->canvasWidget->finishResizingImage(w, h); } void KisCanvas2::startUpdateCanvasProjection(const QRect & rc) { KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags); if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) { emit sigCanvasCacheUpdated(); } } void KisCanvas2::updateCanvasProjection() { auto tryIssueCanvasUpdates = [this](const QRect &vRect) { if (!m_d->isBatchUpdateActive) { // TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas if (m_d->currentCanvasIsOpenGL) { m_d->savedUpdateRect = QRect(); // we already had a compression in frameRenderStartCompressor, so force the update directly slotDoCanvasUpdate(); } else if (/* !m_d->currentCanvasIsOpenGL && */ !vRect.isEmpty()) { m_d->savedUpdateRect = m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect(); // we already had a compression in frameRenderStartCompressor, so force the update directly slotDoCanvasUpdate(); } } }; auto uploadData = [this, tryIssueCanvasUpdates](const QVector &infoObjects) { QVector viewportRects = m_d->canvasWidget->updateCanvasProjection(infoObjects); const QRect vRect = std::accumulate(viewportRects.constBegin(), viewportRects.constEnd(), QRect(), std::bit_or()); tryIssueCanvasUpdates(vRect); }; bool shouldExplicitlyIssueUpdates = false; QVector infoObjects; KisUpdateInfoList originalInfoObjects; m_d->projectionUpdatesCompressor.takeUpdateInfo(originalInfoObjects); for (auto it = originalInfoObjects.constBegin(); it != originalInfoObjects.constEnd(); ++it) { KisUpdateInfoSP info = *it; const KisMarkerUpdateInfo *batchInfo = dynamic_cast(info.data()); if (batchInfo) { if (!infoObjects.isEmpty()) { uploadData(infoObjects); infoObjects.clear(); } if (batchInfo->type() == KisMarkerUpdateInfo::StartBatch) { m_d->isBatchUpdateActive++; } else if (batchInfo->type() == KisMarkerUpdateInfo::EndBatch) { m_d->isBatchUpdateActive--; KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->isBatchUpdateActive >= 0); if (m_d->isBatchUpdateActive == 0) { shouldExplicitlyIssueUpdates = true; } } else if (batchInfo->type() == KisMarkerUpdateInfo::BlockLodUpdates) { m_d->canvasWidget->setLodResetInProgress(true); } else if (batchInfo->type() == KisMarkerUpdateInfo::UnblockLodUpdates) { m_d->canvasWidget->setLodResetInProgress(false); shouldExplicitlyIssueUpdates = true; } } else { infoObjects << info; } } if (!infoObjects.isEmpty()) { uploadData(infoObjects); } else if (shouldExplicitlyIssueUpdates) { tryIssueCanvasUpdates(m_d->coordinatesConverter->imageRectInImagePixels()); } } void KisCanvas2::slotBeginUpdatesBatch() { KisUpdateInfoSP info = new KisMarkerUpdateInfo(KisMarkerUpdateInfo::StartBatch, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotEndUpdatesBatch() { KisUpdateInfoSP info = new KisMarkerUpdateInfo(KisMarkerUpdateInfo::EndBatch, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotSetLodUpdatesBlocked(bool value) { KisUpdateInfoSP info = new KisMarkerUpdateInfo(value ? KisMarkerUpdateInfo::BlockLodUpdates : KisMarkerUpdateInfo::UnblockLodUpdates, m_d->coordinatesConverter->imageRectInImagePixels()); m_d->projectionUpdatesCompressor.putUpdateInfo(info); emit sigCanvasCacheUpdated(); } void KisCanvas2::slotDoCanvasUpdate() { /** * WARNING: in isBusy() we access openGL functions without making the painting * context current. We hope that currently active context will be Qt's one, * which is shared with our own. */ if (m_d->canvasWidget->isBusy()) { // just restarting the timer updateCanvasWidgetImpl(m_d->savedUpdateRect); return; } if (m_d->savedUpdateRect.isEmpty()) { m_d->canvasWidget->widget()->update(); emit updateCanvasRequested(m_d->canvasWidget->widget()->rect()); } else { emit updateCanvasRequested(m_d->savedUpdateRect); m_d->canvasWidget->widget()->update(m_d->savedUpdateRect); } m_d->savedUpdateRect = QRect(); } void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc) { if (!m_d->canvasUpdateCompressor.isActive() || !m_d->savedUpdateRect.isEmpty()) { m_d->savedUpdateRect |= rc; } m_d->canvasUpdateCompressor.start(); } void KisCanvas2::updateCanvas() { updateCanvasWidgetImpl(); } void KisCanvas2::updateCanvas(const QRectF& documentRect) { if (m_d->currentCanvasIsOpenGL && m_d->canvasWidget->decorations().size() > 0) { updateCanvasWidgetImpl(); } else { // updateCanvas is called from tools, never from the projection // updates, so no need to prescale! QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect(); widgetRect.adjust(-2, -2, 2, 2); if (!widgetRect.isEmpty()) { updateCanvasWidgetImpl(widgetRect); } } } void KisCanvas2::disconnectCanvasObserver(QObject *object) { KoCanvasBase::disconnectCanvasObserver(object); m_d->view->disconnect(object); } void KisCanvas2::notifyZoomChanged() { if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->notifyZoomChanged(); } notifyLevelOfDetailChange(); updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction m_d->regionOfInterestUpdateCompressor.start(); } QRect KisCanvas2::regionOfInterest() const { return m_d->regionOfInterest; } void KisCanvas2::slotUpdateRegionOfInterest() { const QRect oldRegionOfInterest = m_d->regionOfInterest; const qreal ratio = 0.25; const QRect proposedRoi = KisAlgebra2D::blowRect(m_d->coordinatesConverter->widgetRectInImagePixels(), ratio).toAlignedRect(); const QRect imageRect = m_d->coordinatesConverter->imageRectInImagePixels(); m_d->regionOfInterest = imageRect.contains(proposedRoi) ? proposedRoi : imageRect; if (m_d->regionOfInterest != oldRegionOfInterest) { emit sigRegionOfInterestChanged(m_d->regionOfInterest); } } void KisCanvas2::slotReferenceImagesChanged() { canvasController()->resetScrollBars(); } void KisCanvas2::setRenderingLimit(const QRect &rc) { m_d->renderingLimit = rc; } QRect KisCanvas2::renderingLimit() const { return m_d->renderingLimit; } void KisCanvas2::slotTrySwitchShapeManager() { KisNodeSP node = m_d->view->currentNode(); QPointer newManager; newManager = fetchShapeManagerFromNode(node); m_d->setActiveShapeManager(newManager); } void KisCanvas2::notifyLevelOfDetailChange() { if (!m_d->effectiveLodAllowedInImage()) return; const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom(); KisConfig cfg(true); const int maxLod = cfg.numMipmapLevels(); const int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod); if (m_d->effectiveLodAllowedInImage()) { KisImageSP image = this->image(); image->setDesiredLevelOfDetail(lod); } } const KoColorProfile * KisCanvas2::monitorProfile() { return m_d->displayColorConverter.monitorProfile(); } KisViewManager* KisCanvas2::viewManager() const { if (m_d->view) { return m_d->view->viewManager(); } return 0; } QPointerKisCanvas2::imageView() const { return m_d->view; } KisImageWSP KisCanvas2::image() const { return m_d->view->image(); } KisImageWSP KisCanvas2::currentImage() const { return m_d->view->image(); } void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset) { QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); // The given offset is in widget logical pixels. In order to prevent fuzzy // canvas rendering at 100% pixel-perfect zoom level when devicePixelRatio // is not integral, we adjusts the offset to map to whole device pixels. // // FIXME: This is a temporary hack for fixing the canvas under fractional // DPI scaling before a new coordinate system is introduced. QPointF offsetAdjusted = m_d->coordinatesConverter->snapToDevicePixel(documentOffset); m_d->coordinatesConverter->setDocumentOffset(offsetAdjusted); QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); QPointF moveOffset = offsetAfter - offsetBefore; if (!m_d->currentCanvasIsOpenGL) m_d->prescaledProjection->viewportMoved(moveOffset); emit documentOffsetUpdateFinished(); updateCanvas(); m_d->regionOfInterestUpdateCompressor.start(); } void KisCanvas2::slotConfigChanged() { KisConfig cfg(true); m_d->vastScrolling = cfg.vastScrolling(); resetCanvas(cfg.useOpenGL()); - setDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this->canvasWidget()))); + + // HACK: Sometimes screenNumber(this->canvasWidget()) is not able to get the + // proper screenNumber when moving the window across screens. Using + // the coordinates should be able to work around this. + // FIXME: We should change to associate the display profiles with the screen + // model and serial number instead. See https://bugs.kde.org/show_bug.cgi?id=407498 + QScreen *canvasScreen = this->canvasWidget()->window()->windowHandle()->screen(); + QPoint canvasScreenCenter = canvasScreen->geometry().center(); + int canvasScreenNumber = QApplication::desktop()->screenNumber(canvasScreenCenter); + if (canvasScreenNumber == -1) { + // Fall back to the old way of getting the screenNumber + canvasScreenNumber = QApplication::desktop()->screenNumber(this->canvasWidget()); + } + if (canvasScreenNumber != -1) { + setDisplayProfile(cfg.displayProfile(canvasScreenNumber)); + } else { + warnUI << "Failed to get screenNumber for updating display profile."; + } initializeFpsDecoration(); } void KisCanvas2::refetchDataFromImage() { KisImageSP image = this->image(); KisImageBarrierLocker l(image); startUpdateInPatches(image->bounds()); } void KisCanvas2::setDisplayProfile(const KoColorProfile *monitorProfile) { if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return; m_d->displayColorConverter.setMonitorProfile(monitorProfile); { KisImageSP image = this->image(); KisImageBarrierLocker l(image); m_d->canvasWidget->setDisplayColorConverter(&m_d->displayColorConverter); } refetchDataFromImage(); } void KisCanvas2::addDecoration(KisCanvasDecorationSP deco) { m_d->canvasWidget->addDecoration(deco); } KisCanvasDecorationSP KisCanvas2::decoration(const QString& id) const { return m_d->canvasWidget->decoration(id); } QPoint KisCanvas2::documentOrigin() const { /** * In Krita we don't use document origin anymore. * All the centering when needed (vastScrolling < 0.5) is done * automatically by the KisCoordinatesConverter. */ return QPoint(); } QPoint KisCanvas2::documentOffset() const { return m_d->coordinatesConverter->documentOffset(); } void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager) { m_d->popupPalette = new KisPopupPalette(viewManager(), m_d->coordinatesConverter, favoriteResourceManager, displayColorConverter()->displayRendererInterface(), m_d->view->resourceProvider(), m_d->canvasWidget->widget()); connect(m_d->popupPalette, SIGNAL(zoomLevelChanged(int)), this, SLOT(slotPopupPaletteRequestedZoomChange(int))); connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas())); connect(m_d->view->mainWindow(), SIGNAL(themeChanged()), m_d->popupPalette, SLOT(slotUpdateIcons())); m_d->popupPalette->showPopupPalette(false); } void KisCanvas2::slotPopupPaletteRequestedZoomChange(int zoom ) { m_d->view->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, (qreal)(zoom/100.0)); // 1.0 is 100% zoom notifyZoomChanged(); } void KisCanvas2::setCursor(const QCursor &cursor) { canvasWidget()->setCursor(cursor); } KisAnimationFrameCacheSP KisCanvas2::frameCache() const { return m_d->frameCache; } KisAnimationPlayer *KisCanvas2::animationPlayer() const { return m_d->animationPlayer; } void KisCanvas2::slotSelectionChanged() { KisShapeLayer* shapeLayer = dynamic_cast(viewManager()->activeLayer().data()); if (!shapeLayer) { return; } m_d->shapeManager.selection()->deselectAll(); Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) { m_d->shapeManager.selection()->select(shape); } } bool KisCanvas2::isPopupPaletteVisible() const { if (!m_d->popupPalette) { return false; } return m_d->popupPalette->isVisible(); } void KisCanvas2::setWrapAroundViewingMode(bool value) { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { infinityDecoration->setVisible(!value); } m_d->canvasWidget->setWrapAroundViewingMode(value); } bool KisCanvas2::wrapAroundViewingMode() const { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { return !(infinityDecoration->visible()); } return false; } void KisCanvas2::bootstrapFinished() { if (!m_d->bootstrapLodBlocked) return; m_d->bootstrapLodBlocked = false; setLodAllowedInCanvas(m_d->lodAllowedInImage); } void KisCanvas2::setLodAllowedInCanvas(bool value) { if (!KisOpenGL::supportsLoD()) { qWarning() << "WARNING: Level of Detail functionality is available only with openGL + GLSL 1.3 support"; } m_d->lodAllowedInImage = value && m_d->currentCanvasIsOpenGL && KisOpenGL::supportsLoD() && (m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode || m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering); KisImageSP image = this->image(); if (m_d->effectiveLodAllowedInImage() != !image->levelOfDetailBlocked()) { image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInImage()); } notifyLevelOfDetailChange(); KisConfig cfg(false); cfg.setLevelOfDetailEnabled(m_d->lodAllowedInImage); } bool KisCanvas2::lodAllowedInCanvas() const { return m_d->lodAllowedInImage; } void KisCanvas2::slotShowPopupPalette(const QPoint &p) { if (!m_d->popupPalette) { return; } m_d->popupPalette->showPopupPalette(p); } KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const { KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration"); return qobject_cast(deco.data()); } KisReferenceImagesDecorationSP KisCanvas2::referenceImagesDecoration() const { KisCanvasDecorationSP deco = decoration("referenceImagesDecoration"); return qobject_cast(deco.data()); } diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc index dadfd0f31d..8c4f9e2e3d 100644 --- a/libs/ui/flake/kis_shape_layer.cc +++ b/libs/ui/flake/kis_shape_layer.cc @@ -1,724 +1,724 @@ /* * Copyright (c) 2006-2008 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * Copyright (c) 2009 Cyrille Berger * Copyright (c) 2011 Jan Hambrecht * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_shape_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SvgWriter.h" #include "SvgParser.h" #include #include #include "kis_default_bounds.h" #include #include "kis_shape_layer_canvas.h" #include "kis_image_view_converter.h" #include #include "kis_node_visitor.h" #include "kis_processing_visitor.h" #include "kis_effect_mask.h" #include "commands/KoShapeReorderCommand.h" #include class ShapeLayerContainerModel : public SimpleShapeContainerModel { public: ShapeLayerContainerModel(KisShapeLayer *parent) : q(parent) {} void add(KoShape *child) override { SimpleShapeContainerModel::add(child); /** * The shape is always added with the absolute transformation set appropriately. * Here we should just squeeze it into the layer's transformation. */ KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(0); child->applyAbsoluteTransformation(parentTransform.inverted()); } } void remove(KoShape *child) override { KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child)); if (inheritsTransform(child)) { QTransform parentTransform = q->absoluteTransformation(0); child->applyAbsoluteTransformation(parentTransform); } SimpleShapeContainerModel::remove(child); } void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override { q->shapeManager()->addShape(shape); SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree); } void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override { q->shapeManager()->remove(shape); SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree); } private: KisShapeLayer *q; }; struct KisShapeLayer::Private { public: Private() : canvas(0) , controller(0) , x(0) , y(0) {} KisPaintDeviceSP paintDevice; KisShapeLayerCanvasBase * canvas; KoShapeControllerBase* controller; int x; int y; }; KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller, KisImageWSP image, const QString &name, quint8 opacity) : KisExternalLayer(image, name, opacity), KoShapeLayer(new ShapeLayerContainerModel(this)), m_d(new Private()) { initShapeLayer(controller); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& rhs) : KisShapeLayer(rhs, rhs.m_d->controller) { } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeControllerBase* controller, KisShapeLayerCanvasBase *canvas) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // copy the projection to avoid extra round of updates! initShapeLayer(controller, _rhs.m_d->paintDevice, canvas); /** * The transformaitons of the added shapes are automatically merged into the transformation * of the layer, so we should apply this extra transform separately */ const QTransform thisInvertedTransform = this->absoluteTransformation(0).inverted(); m_d->canvas->setUpdatesBlocked(true); Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform); addShape(clonedShape); } - m_d->canvas->setUpdatesBlocked(true); + m_d->canvas->setUpdatesBlocked(false); } KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes) : KisExternalLayer(_rhs) , KoShapeLayer(new ShapeLayerContainerModel(this)) //no _merge here otherwise both layer have the same KoShapeContainerModel , m_d(new Private()) { // Make sure our new layer is visible otherwise the shapes cannot be painted. setVisible(true); initShapeLayer(_rhs.m_d->controller); /** * With current implementation this matrix will always be an identity, because * we do not copy the transformation from any of the source layers. But we should * handle this anyway, to not be caught by this in the future. */ const QTransform thisInvertedTransform = this->absoluteTransformation(0).inverted(); QList shapesAbove; QList shapesBelow; // copy in _rhs's shapes Q_FOREACH (KoShape *shape, _rhs.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform); shapesBelow.append(clonedShape); } // copy in _addShapes's shapes Q_FOREACH (KoShape *shape, _addShapes.shapes()) { KoShape *clonedShape = shape->cloneShape(); KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; } clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform); shapesAbove.append(clonedShape); } QList shapes = KoShapeReorderCommand::mergeDownShapes(shapesBelow, shapesAbove); KoShapeReorderCommand cmd(shapes); cmd.redo(); Q_FOREACH (KoShape *shape, shapesBelow + shapesAbove) { addShape(shape); } } KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller, KisImageWSP image, const QString &name, quint8 opacity, KisShapeLayerCanvasBase *canvas) : KisExternalLayer(image, name, opacity) , KoShapeLayer(new ShapeLayerContainerModel(this)) , m_d(new Private()) { initShapeLayer(controller, nullptr, canvas); } KisShapeLayer::~KisShapeLayer() { /** * Small hack alert: we should avoid updates on shape deletion */ m_d->canvas->prepareForDestroying(); Q_FOREACH (KoShape *shape, shapes()) { shape->setParent(0); delete shape; } delete m_d->canvas; delete m_d; } void KisShapeLayer::initShapeLayer(KoShapeControllerBase* controller, KisPaintDeviceSP copyFromProjection, KisShapeLayerCanvasBase *canvas) { setSupportsLodMoves(false); setShapeId(KIS_SHAPE_LAYER_ID); KIS_ASSERT_RECOVER_NOOP(this->image()); if (!copyFromProjection) { m_d->paintDevice = new KisPaintDevice(image()->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image())); m_d->paintDevice->setParentNode(this); } else { m_d->paintDevice = new KisPaintDevice(*copyFromProjection); } if (!canvas) { auto *slCanvas = new KisShapeLayerCanvas(this, image()); slCanvas->setProjection(m_d->paintDevice); canvas = slCanvas; } m_d->canvas = canvas; m_d->canvas->moveToThread(this->thread()); m_d->controller = controller; m_d->canvas->shapeManager()->selection()->disconnect(this); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged())); connect(m_d->canvas->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), this, SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF))); } bool KisShapeLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } void KisShapeLayer::setImage(KisImageWSP _image) { KisLayer::setImage(_image); m_d->canvas->setImage(_image); m_d->paintDevice->convertTo(_image->colorSpace()); m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(_image)); } KisLayerSP KisShapeLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { KisShapeLayer *prevShape = dynamic_cast(prevLayer.data()); if (prevShape) return new KisShapeLayer(*prevShape, *this); else return KisExternalLayer::createMergedLayerTemplate(prevLayer); } void KisShapeLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { if (!dynamic_cast(dstLayer.data())) { KisLayer::fillMergedLayerTemplate(dstLayer, prevLayer); } } void KisShapeLayer::setParent(KoShapeContainer *parent) { Q_UNUSED(parent) KIS_ASSERT_RECOVER_RETURN(0) } QIcon KisShapeLayer::icon() const { return KisIconUtils::loadIcon("vectorLayer"); } KisPaintDeviceSP KisShapeLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisShapeLayer::paintDevice() const { return 0; } qint32 KisShapeLayer::x() const { return m_d->x; } qint32 KisShapeLayer::y() const { return m_d->y; } void KisShapeLayer::setX(qint32 x) { qint32 delta = x - this->x(); QPointF diff = QPointF(m_d->canvas->viewConverter()->viewToDocumentX(delta), 0); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->x = x; } void KisShapeLayer::setY(qint32 y) { qint32 delta = y - this->y(); QPointF diff = QPointF(0, m_d->canvas->viewConverter()->viewToDocumentY(delta)); emit sigMoveShapes(diff); // Save new value to satisfy LSP m_d->y = y; } namespace { void filterTransformableShapes(QList &shapes) { auto it = shapes.begin(); while (it != shapes.end()) { if (shapes.size() == 1) break; if ((*it)->inheritsTransformFromAny(shapes)) { it = shapes.erase(it); } else { ++it; } } } } QList KisShapeLayer::shapesToBeTransformed() { QList shapes = shapeManager()->shapes(); // We expect that **all** the shapes inherit the transform from its parent // SANITY_CHECK: we expect all the shapes inside the // shape layer to inherit transform! Q_FOREACH (KoShape *shape, shapes) { if (shape->parent()) { KIS_SAFE_ASSERT_RECOVER(shape->parent()->inheritsTransform(shape)) { break; } } } shapes << this; filterTransformableShapes(shapes); return shapes; } void KisShapeLayer::slotMoveShapes(const QPointF &diff) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return; KoShapeMoveCommand cmd(shapes, diff); cmd.redo(); } bool KisShapeLayer::accept(KisNodeVisitor& visitor) { return visitor.visit(this); } void KisShapeLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { return visitor.visit(this, undoAdapter); } KoShapeManager* KisShapeLayer::shapeManager() const { return m_d->canvas->shapeManager(); } KoViewConverter* KisShapeLayer::converter() const { return m_d->canvas->viewConverter(); } bool KisShapeLayer::visible(bool recursive) const { return KisExternalLayer::visible(recursive); } void KisShapeLayer::setVisible(bool visible, bool isLoading) { const bool oldVisible = this->visible(false); KoShapeLayer::setVisible(visible); KisExternalLayer::setVisible(visible, isLoading); if (visible && !oldVisible && m_d->canvas->hasChangedWhileBeingInvisible()) { m_d->canvas->rerenderAfterBeingInvisible(); } } void KisShapeLayer::setUserLocked(bool value) { KoShapeLayer::setGeometryProtected(value); KisExternalLayer::setUserLocked(value); } bool KisShapeLayer::isShapeEditable(bool recursive) const { return KoShapeLayer::isShapeEditable(recursive) && isEditable(true); } // we do not override KoShape::setGeometryProtected() as we consider // the user not being able to access the layer shape from Krita UI! void KisShapeLayer::forceUpdateTimedNode() { m_d->canvas->forceRepaint(); } bool KisShapeLayer::hasPendingTimedUpdates() const { return m_d->canvas->hasPendingUpdates(); } bool KisShapeLayer::saveShapesToStore(KoStore *store, QList shapes, const QSizeF &sizeInPt) { if (!store->open("content.svg")) { return false; } KoStoreDevice storeDev(store); storeDev.open(QIODevice::WriteOnly); std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); SvgWriter writer(shapes); writer.save(storeDev, sizeInPt); if (!store->close()) { return false; } return true; } QList KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize) { QString errorMsg; int errorLine = 0; int errorColumn; KoXmlDocument doc = SvgParser::createDocumentFromSvg(device, &errorMsg, &errorLine, &errorColumn); if (doc.isNull()) { errKrita << "Parsing error in " << "contents.svg" << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3" , errorLine , errorColumn , errorMsg); } SvgParser parser(resourceManager); parser.setXmlBaseDir(baseXmlDir); parser.setResolution(rectInPixels /* px */, resolutionPPI /* ppi */); return parser.parseSvg(doc.documentElement(), fragmentSize); } bool KisShapeLayer::saveLayer(KoStore * store) const { // FIXME: we handle xRes() only! const QSizeF sizeInPx = image()->bounds().size(); const QSizeF sizeInPt(sizeInPx.width() / image()->xRes(), sizeInPx.height() / image()->yRes()); return saveShapesToStore(store, this->shapes(), sizeInPt); } bool KisShapeLayer::loadSvg(QIODevice *device, const QString &baseXmlDir) { QSizeF fragmentSize; // unused! KisImageSP image = this->image(); // FIXME: we handle xRes() only! KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(image->xRes(), image->yRes())); const qreal resolutionPPI = 72.0 * image->xRes(); QList shapes = createShapesFromSvg(device, baseXmlDir, image->bounds(), resolutionPPI, m_d->controller->resourceManager(), &fragmentSize); Q_FOREACH (KoShape *shape, shapes) { addShape(shape); } return true; } bool KisShapeLayer::loadLayer(KoStore* store) { if (!store) { warnKrita << i18n("No store backend"); return false; } if (store->open("content.svg")) { KoStoreDevice storeDev(store); storeDev.open(QIODevice::ReadOnly); loadSvg(&storeDev, ""); store->close(); return true; } KoOdfReadStore odfStore(store); QString errorMessage; odfStore.loadAndParse(errorMessage); if (!errorMessage.isEmpty()) { warnKrita << errorMessage; return false; } KoXmlElement contents = odfStore.contentDoc().documentElement(); // dbgKrita <<"Start loading OASIS document..." << contents.text(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI(); // dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement(); KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body")); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) ); return false; } body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing"); if (body.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) ); return false; } KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page")); if (page.isNull()) { //setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) ); return false; } KoXmlElement * master = 0; if (odfStore.styles().masterPages().contains("Standard")) master = odfStore.styles().masterPages().value("Standard"); else if (odfStore.styles().masterPages().contains("Default")) master = odfStore.styles().masterPages().value("Default"); else if (! odfStore.styles().masterPages().empty()) master = odfStore.styles().masterPages().begin().value(); if (master) { const KoXmlElement *style = odfStore.styles().findStyle( master->attributeNS(KoXmlNS::style, "page-layout-name", QString())); KoPageLayout pageLayout; pageLayout.loadOdf(*style); setSize(QSizeF(pageLayout.width, pageLayout.height)); } // We work fine without a master page KoOdfLoadingContext context(odfStore.styles(), odfStore.store()); context.setManifestFile(QString("tar:/") + odfStore.store()->currentPath() + "META-INF/manifest.xml"); KoShapeLoadingContext shapeContext(context, m_d->controller->resourceManager()); KoXmlElement layerElement; forEachElement(layerElement, context.stylesReader().layerSet()) { // FIXME: investigate what is this // KoShapeLayer * l = new KoShapeLayer(); if (!loadOdf(layerElement, shapeContext)) { dbgKrita << "Could not load vector layer!"; return false; } } KoXmlElement child; forEachElement(child, page) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext); if (shape) { addShape(shape); } } return true; } void KisShapeLayer::resetCache() { m_d->canvas->resetCache(); } KUndo2Command* KisShapeLayer::crop(const QRect & rect) { QPoint oldPos(x(), y()); QPoint newPos = oldPos - rect.topLeft(); return new KisNodeMoveCommand2(this, oldPos, newPos); } KUndo2Command* KisShapeLayer::transform(const QTransform &transform) { QList shapes = shapesToBeTransformed(); if (shapes.isEmpty()) return 0; KisImageViewConverter *converter = dynamic_cast(this->converter()); QTransform realTransform = converter->documentToView() * transform * converter->viewToDocument(); QList oldTransformations; QList newTransformations; QList newShadows; const qreal transformBaseScale = KoUnit::approxTransformScale(transform); Q_FOREACH (const KoShape* shape, shapes) { QTransform oldTransform = shape->transformation(); oldTransformations.append(oldTransform); QTransform globalTransform = shape->absoluteTransformation(0); QTransform localTransform = globalTransform * realTransform * globalTransform.inverted(); newTransformations.append(localTransform * oldTransform); KoShapeShadow *shadow = 0; if (shape->shadow()) { shadow = new KoShapeShadow(*shape->shadow()); shadow->setOffset(transformBaseScale * shadow->offset()); shadow->setBlur(transformBaseScale * shadow->blur()); } newShadows.append(shadow); } KUndo2Command *parentCommand = new KUndo2Command(); new KoShapeTransformCommand(shapes, oldTransformations, newTransformations, parentCommand); new KoShapeShadowCommand(shapes, newShadows, parentCommand); return parentCommand; } KoShapeControllerBase *KisShapeLayer::shapeController() const { return m_d->controller; } diff --git a/libs/ui/kis_animation_importer.cpp b/libs/ui/kis_animation_importer.cpp index f43cb43677..a4c69aab8a 100644 --- a/libs/ui/kis_animation_importer.cpp +++ b/libs/ui/kis_animation_importer.cpp @@ -1,130 +1,130 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_animation_importer.h" #include #include "KoColorSpace.h" #include #include #include "KisPart.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_undo_adapter.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_raster_keyframe_channel.h" #include "commands/kis_image_layer_add_command.h" struct KisAnimationImporter::Private { KisImageSP image; KisDocument *document; bool stop; KoUpdaterPtr updater; }; KisAnimationImporter::KisAnimationImporter(KisImageSP image, KoUpdaterPtr updater) : m_d(new Private()) { m_d->document = 0; m_d->image = image; m_d->stop = false; m_d->updater = updater; } KisAnimationImporter::KisAnimationImporter(KisDocument* document) : m_d(new Private()) { m_d->document= document; m_d->image = document->image(); m_d->stop = false; } KisAnimationImporter::~KisAnimationImporter() {} -KisImportExportFilter::ConversionStatus KisAnimationImporter::import(QStringList files, int firstFrame, int step) +KisImportExportErrorCode KisAnimationImporter::import(QStringList files, int firstFrame, int step) { Q_ASSERT(step > 0); m_d->image->lock(); KisUndoAdapter *undo = m_d->image->undoAdapter(); undo->beginMacro(kundo2_i18n("Import animation")); QScopedPointer importDoc(KisPart::instance()->createDocument()); importDoc->setFileBatchMode(true); - KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK; + KisImportExportErrorCode status = ImportExportCodes::OK; int frame = firstFrame; int filesProcessed = 0; if (m_d->updater) { m_d->updater->setRange(0, files.size()); } KisRasterKeyframeChannel *contentChannel = 0; Q_FOREACH(QString file, files) { bool successfullyLoaded = importDoc->openUrl(QUrl::fromLocalFile(file), KisDocument::DontAddToRecent); if (!successfullyLoaded) { - status = KisImportExportFilter::InternalError; + status = ImportExportCodes::InternalError; break; } if (frame == firstFrame) { const KoColorSpace *cs = importDoc->image()->colorSpace(); KisPaintLayerSP paintLayer = new KisPaintLayer(m_d->image, m_d->image->nextLayerName(), OPACITY_OPAQUE_U8, cs); undo->addCommand(new KisImageLayerAddCommand(m_d->image, paintLayer, m_d->image->rootLayer(), m_d->image->rootLayer()->childCount())); paintLayer->enableAnimation(); contentChannel = qobject_cast(paintLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); } if (m_d->updater) { if (m_d->updater->interrupted()) { m_d->stop = true; } else { m_d->updater->setValue(filesProcessed + 1); // the updater doesn't call that automatically, // it is "threaded" by default qApp->processEvents(); } } if (m_d->stop) { - status = KisImportExportFilter::ProgressCancelled; + status = ImportExportCodes::Cancelled; break; } contentChannel->importFrame(frame, importDoc->image()->projection(), NULL); frame += step; filesProcessed++; } undo->endMacro(); m_d->image->unlock(); return status; } void KisAnimationImporter::cancel() { m_d->stop = true; } diff --git a/libs/ui/kis_animation_importer.h b/libs/ui/kis_animation_importer.h index 565db0ecd7..22514080d4 100644 --- a/libs/ui/kis_animation_importer.h +++ b/libs/ui/kis_animation_importer.h @@ -1,48 +1,49 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_ANIMATION_IMPORTER_H #define KIS_ANIMATION_IMPORTER_H #include "kis_types.h" #include "kritaui_export.h" #include +#include class KisDocument; class KisMainWindow; class KRITAUI_EXPORT KisAnimationImporter : public QObject { Q_OBJECT public: KisAnimationImporter(KisImageSP image, KoUpdaterPtr updater = 0); KisAnimationImporter(KisDocument* document); ~KisAnimationImporter() override; - KisImportExportFilter::ConversionStatus import(QStringList files, int firstFrame, int step); + KisImportExportErrorCode import(QStringList files, int firstFrame, int step); private Q_SLOTS: void cancel(); private: struct Private; QScopedPointer m_d; }; #endif diff --git a/libs/ui/kis_async_action_feedback.cpp b/libs/ui/kis_async_action_feedback.cpp index 255e758d48..e5af581bc2 100644 --- a/libs/ui/kis_async_action_feedback.cpp +++ b/libs/ui/kis_async_action_feedback.cpp @@ -1,87 +1,87 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_async_action_feedback.h" #include #include struct KisAsyncActionFeedback::Private { QScopedPointer progress; }; KisAsyncActionFeedback::KisAsyncActionFeedback(const QString &message, QWidget *parent) : m_d(new Private) { m_d->progress.reset(new QProgressDialog(message, "", 0, 0, parent)); m_d->progress->setWindowModality(Qt::ApplicationModal); m_d->progress->setCancelButton(0); m_d->progress->setMinimumDuration(1000); m_d->progress->setValue(0); // disable close button m_d->progress->setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint); } KisAsyncActionFeedback::~KisAsyncActionFeedback() { } template T runActionImpl(std::function func) { QFuture result = QtConcurrent::run(func); QFutureWatcher watcher; watcher.setFuture(result); while (watcher.isRunning()) { qApp->processEvents(); } watcher.waitForFinished(); return watcher.result(); } -KisImportExportFilter::ConversionStatus KisAsyncActionFeedback::runAction(std::function func) +KisImportExportErrorCode KisAsyncActionFeedback::runAction(std::function func) { return runActionImpl(func); } void KisAsyncActionFeedback::runVoidAction(std::function func) { QFuture result = QtConcurrent::run(func); QFutureWatcher watcher; watcher.setFuture(result); while (watcher.isRunning()) { qApp->processEvents(); } watcher.waitForFinished(); } void KisAsyncActionFeedback::waitForMutex(QMutex *mutex) { while (!mutex->tryLock()) { qApp->processEvents(); } mutex->unlock(); } diff --git a/libs/ui/kis_async_action_feedback.h b/libs/ui/kis_async_action_feedback.h index a615a45585..59e070fd84 100644 --- a/libs/ui/kis_async_action_feedback.h +++ b/libs/ui/kis_async_action_feedback.h @@ -1,44 +1,44 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_ASYNC_ACTION_FEEDBACK_H #define __KIS_ASYNC_ACTION_FEEDBACK_H #include #include #include "KisImportExportFilter.h" class QWidget; class QMutex; class KisAsyncActionFeedback { public: KisAsyncActionFeedback(const QString &message, QWidget *parent); ~KisAsyncActionFeedback(); - KisImportExportFilter::ConversionStatus runAction(std::function func); + KisImportExportErrorCode runAction(std::function func); void runVoidAction(std::function func); void waitForMutex(QMutex *mutex); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_ASYNC_ACTION_FEEDBACK_H */ diff --git a/libs/ui/kis_mimedata.cpp b/libs/ui/kis_mimedata.cpp index dec24428e1..e30f77c7bf 100644 --- a/libs/ui/kis_mimedata.cpp +++ b/libs/ui/kis_mimedata.cpp @@ -1,471 +1,471 @@ /* * Copyright (c) 2011 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_mimedata.h" #include "kis_config.h" #include "kis_node.h" #include "kis_paint_device.h" #include "kis_shared_ptr.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_shape_layer.h" #include "kis_paint_layer.h" #include "KisDocument.h" #include "kis_shape_controller.h" #include "KisPart.h" #include "kis_layer_utils.h" #include "kis_node_insertion_adapter.h" #include "kis_dummies_facade_base.h" #include "kis_node_dummies_graph.h" #include "KisImportExportManager.h" #include "KisImageBarrierLockerWithFeedback.h" #include #include #include #include #include #include #include #include #include #include #include #include KisMimeData::KisMimeData(QList nodes, KisImageSP image, bool forceCopy) : QMimeData() , m_nodes(nodes) , m_forceCopy(forceCopy) , m_image(image) { Q_ASSERT(m_nodes.size() > 0); } void KisMimeData::deepCopyNodes() { KisNodeList newNodes; { KisImageBarrierLockerWithFeedbackAllowNull locker(m_image); Q_FOREACH (KisNodeSP node, m_nodes) { newNodes << node->clone(); } } m_nodes = newNodes; m_image = 0; } QList KisMimeData::nodes() const { return m_nodes; } QStringList KisMimeData::formats () const { QStringList f = QMimeData::formats(); if (m_nodes.size() > 0) { f << "application/x-krita-node" << "application/x-krita-node-url" << "application/x-qt-image" << "application/zip" << "application/x-krita-node-internal-pointer"; } return f; } KisDocument *createDocument(QList nodes, KisImageSP srcImage) { KisDocument *doc = KisPart::instance()->createDocument(); QRect rc; Q_FOREACH (KisNodeSP node, nodes) { rc |= node->exactBounds(); } KisImageSP image = new KisImage(0, rc.width(), rc.height(), nodes.first()->colorSpace(), nodes.first()->name()); image->setAllowMasksOnRootNode(true); { KisImageBarrierLockerWithFeedbackAllowNull locker(srcImage); Q_FOREACH (KisNodeSP node, nodes) { image->addNode(node->clone()); } } doc->setCurrentImage(image); return doc; } QByteArray serializeToByteArray(QList nodes, KisImageSP srcImage) { QScopedPointer doc(createDocument(nodes, srcImage)); QByteArray result = doc->serializeToNativeByteArray(); // avoid a sanity check failure caused by the fact that the image outlives // the document (and it does) doc->setCurrentImage(0); return result; } QVariant KisMimeData::retrieveData(const QString &mimetype, QVariant::Type preferredType) const { /** * HACK ALERT: * * Sometimes Qt requests the data *after* destruction of Krita, * we cannot load the nodes in that case, because we need signals * and timers. So we just skip serializing. */ if (!QApplication::instance()) return QVariant(); Q_ASSERT(m_nodes.size() > 0); if (mimetype == "application/x-qt-image") { KisConfig cfg(true); KisDocument *doc = createDocument(m_nodes, m_image); return doc->image()->projection()->convertToQImage(cfg.displayProfile(QApplication::desktop()->screenNumber(qApp->activeWindow())), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } else if (mimetype == "application/x-krita-node" || mimetype == "application/zip") { QByteArray ba = serializeToByteArray(m_nodes, m_image); return ba; } else if (mimetype == "application/x-krita-node-url") { QByteArray ba = serializeToByteArray(m_nodes, m_image); QString temporaryPath = QDir::tempPath() + QDir::separator() + QString("krita_tmp_dnd_layer_%1_%2.kra") .arg(QApplication::applicationPid()) .arg(qrand()); QFile file(temporaryPath); file.open(QFile::WriteOnly); file.write(ba); file.flush(); file.close(); return QUrl::fromLocalFile(temporaryPath).toEncoded(); } else if (mimetype == "application/x-krita-node-internal-pointer") { QDomDocument doc("krita_internal_node_pointer"); QDomElement root = doc.createElement("pointer"); root.setAttribute("application_pid", (qint64)QApplication::applicationPid()); root.setAttribute("force_copy", m_forceCopy); root.setAttribute("image_pointer_value", (qint64)m_image.data()); doc.appendChild(root); Q_FOREACH (KisNodeSP node, m_nodes) { QDomElement element = doc.createElement("node"); element.setAttribute("pointer_value", (qint64)node.data()); root.appendChild(element); } return doc.toByteArray(); } else { return QMimeData::retrieveData(mimetype, preferredType); } } void KisMimeData::initializeExternalNode(KisNodeSP *node, KisImageWSP image, KisShapeController *shapeController) { // adjust the link to a correct image object (*node)->setImage(image); KisShapeLayer *shapeLayer = dynamic_cast(node->data()); if (shapeLayer) { // attach the layer to a new shape controller KisShapeLayer *shapeLayer2 = new KisShapeLayer(*shapeLayer, shapeController); *node = shapeLayer2; } } QList KisMimeData::tryLoadInternalNodes(const QMimeData *data, KisImageSP image, KisShapeController *shapeController, bool /* IN-OUT */ ©Node) { QList nodes; bool forceCopy = false; KisImageSP sourceImage; // Qt 4.7 and Qt 5.5 way const KisMimeData *mimedata = qobject_cast(data); if (mimedata) { nodes = mimedata->nodes(); forceCopy = mimedata->m_forceCopy; sourceImage = mimedata->m_image; } // Qt 4.8 way if (nodes.isEmpty() && data->hasFormat("application/x-krita-node-internal-pointer")) { QByteArray nodeXml = data->data("application/x-krita-node-internal-pointer"); QDomDocument doc; doc.setContent(nodeXml); QDomElement element = doc.documentElement(); qint64 pid = element.attribute("application_pid").toLongLong(); forceCopy = element.attribute("force_copy").toInt(); qint64 imagePointerValue = element.attribute("image_pointer_value").toLongLong(); sourceImage = reinterpret_cast(imagePointerValue); if (pid == QApplication::applicationPid()) { QDomNode n = element.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { qint64 pointerValue = e.attribute("pointer_value").toLongLong(); if (pointerValue) { nodes << reinterpret_cast(pointerValue); } } n = n.nextSibling(); } } } if (!nodes.isEmpty() && (forceCopy || copyNode || sourceImage != image)) { KisImageBarrierLockerWithFeedbackAllowNull locker(sourceImage); QList clones; Q_FOREACH (KisNodeSP node, nodes) { node = node->clone(); if ((forceCopy || copyNode) && sourceImage == image) { KisLayerUtils::addCopyOfNameTag(node); } initializeExternalNode(&node, image, shapeController); clones << node; } nodes = clones; copyNode = true; } return nodes; } QList KisMimeData::loadNodes(const QMimeData *data, const QRect &imageBounds, const QPoint &preferredCenter, bool forceRecenter, KisImageWSP image, KisShapeController *shapeController) { bool alwaysRecenter = false; QList nodes; if (data->hasFormat("application/x-krita-node")) { KisDocument *tempDoc = KisPart::instance()->createDocument(); QByteArray ba = data->data("application/x-krita-node"); QBuffer buf(&ba); KisImportExportFilter *filter = tempDoc->importExportManager()->filterForMimeType(tempDoc->nativeFormatMimeType(), KisImportExportManager::Import); filter->setBatchMode(true); - bool result = (filter->convert(tempDoc, &buf) == KisImportExportFilter::OK); + bool result = (filter->convert(tempDoc, &buf).isOk()); if (result) { KisImageWSP tempImage = tempDoc->image(); Q_FOREACH (KisNodeSP node, tempImage->root()->childNodes(QStringList(), KoProperties())) { tempImage->removeNode(node); initializeExternalNode(&node, image, shapeController); nodes << node; } } delete filter; delete tempDoc; } if (nodes.isEmpty() && data->hasFormat("application/x-krita-node-url")) { QByteArray ba = data->data("application/x-krita-node-url"); KisDocument *tempDoc = KisPart::instance()->createDocument(); Q_ASSERT(QUrl::fromEncoded(ba).isLocalFile()); bool result = tempDoc->openUrl(QUrl::fromEncoded(ba)); if (result) { KisImageWSP tempImage = tempDoc->image(); Q_FOREACH (KisNodeSP node, tempImage->root()->childNodes(QStringList(), KoProperties())) { tempImage->removeNode(node); initializeExternalNode(&node, image, shapeController); nodes << node; } } delete tempDoc; QFile::remove(QUrl::fromEncoded(ba).toLocalFile()); } if (nodes.isEmpty() && data->hasImage()) { QImage qimage = qvariant_cast(data->imageData()); KisPaintDeviceSP device = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); device->convertFromQImage(qimage, 0); if (image) { nodes << new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, device); } alwaysRecenter = true; } if (!nodes.isEmpty()) { Q_FOREACH (KisNodeSP node, nodes) { QRect bounds = node->projection()->exactBounds(); if (alwaysRecenter || forceRecenter || (!imageBounds.contains(bounds) && !imageBounds.intersects(bounds))) { QPoint pt = preferredCenter - bounds.center(); node->setX(pt.x()); node->setY(pt.y()); } } } return nodes; } QMimeData* KisMimeData::mimeForLayers(const KisNodeList &nodes, KisImageSP image, bool forceCopy) { KisNodeList inputNodes = nodes; KisNodeList sortedNodes; KisLayerUtils::sortMergableNodes(image->root(), inputNodes, sortedNodes); if (sortedNodes.isEmpty()) return 0; KisMimeData* data = new KisMimeData(sortedNodes, image, forceCopy); return data; } QMimeData* KisMimeData::mimeForLayersDeepCopy(const KisNodeList &nodes, KisImageSP image, bool forceCopy) { KisNodeList inputNodes = nodes; KisNodeList sortedNodes; KisLayerUtils::sortMergableNodes(image->root(), inputNodes, sortedNodes); if (sortedNodes.isEmpty()) return 0; KisMimeData* data = new KisMimeData(sortedNodes, image, forceCopy); data->deepCopyNodes(); return data; } bool nodeAllowsAsChild(KisNodeSP parent, KisNodeList nodes) { bool result = true; Q_FOREACH (KisNodeSP node, nodes) { if (!parent->allowAsChild(node)) { result = false; break; } } return result; } bool correctNewNodeLocation(KisNodeList nodes, KisNodeDummy* &parentDummy, KisNodeDummy* &aboveThisDummy) { KisNodeSP parentNode = parentDummy->node(); bool result = true; if(!nodeAllowsAsChild(parentDummy->node(), nodes)) { aboveThisDummy = parentDummy; parentDummy = parentDummy->parent(); result = (!parentDummy) ? false : correctNewNodeLocation(nodes, parentDummy, aboveThisDummy); } return result; } KisNodeList KisMimeData::loadNodesFast( const QMimeData *data, KisImageSP image, KisShapeController *shapeController, bool ©Node) { QList nodes = KisMimeData::tryLoadInternalNodes(data, image, shapeController, copyNode /* IN-OUT */); if (nodes.isEmpty()) { QRect imageBounds = image->bounds(); nodes = KisMimeData::loadNodes(data, imageBounds, imageBounds.center(), false, image, shapeController); /** * Don't try to move a node originating from another image, * just copy it. */ copyNode = true; } return nodes; } bool KisMimeData::insertMimeLayers(const QMimeData *data, KisImageSP image, KisShapeController *shapeController, KisNodeDummy *parentDummy, KisNodeDummy *aboveThisDummy, bool copyNode, KisNodeInsertionAdapter *nodeInsertionAdapter) { QList nodes = loadNodesFast(data, image, shapeController, copyNode /* IN-OUT */); if (nodes.isEmpty()) return false; bool result = true; if (!correctNewNodeLocation(nodes, parentDummy, aboveThisDummy)) { return false; } KIS_ASSERT_RECOVER(nodeInsertionAdapter) { return false; } Q_ASSERT(parentDummy); KisNodeSP aboveThisNode = aboveThisDummy ? aboveThisDummy->node() : 0; if (copyNode) { nodeInsertionAdapter->addNodes(nodes, parentDummy->node(), aboveThisNode); } else { Q_ASSERT(nodes.first()->graphListener() == image.data()); nodeInsertionAdapter->moveNodes(nodes, parentDummy->node(), aboveThisNode); } return result; } diff --git a/libs/ui/kis_png_converter.cpp b/libs/ui/kis_png_converter.cpp index 5a9c9b0896..bd2440242b 100644 --- a/libs/ui/kis_png_converter.cpp +++ b/libs/ui/kis_png_converter.cpp @@ -1,1393 +1,1394 @@ /* * Copyright (c) 2005-2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_png_converter.h" // A big thank to Glenn Randers-Pehrson for his wonderful // documentation of libpng available at // http://www.libpng.org/pub/png/libpng-1.2.5-manual.html #ifndef PNG_MAX_UINT // Removed in libpng 1.4 #define PNG_MAX_UINT PNG_UINT_31_MAX #endif #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dialogs/kis_dlg_png_import.h" #include "kis_clipboard.h" #include #include "kis_undo_stores.h" +#include + namespace { int getColorTypeforColorSpace(const KoColorSpace * cs , bool alpha) { QString id = cs->id(); if (id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16") { return alpha ? PNG_COLOR_TYPE_GRAY_ALPHA : PNG_COLOR_TYPE_GRAY; } if (id == "RGBA" || id == "RGBA16") { return alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB; } return -1; } bool colorSpaceIdSupported(const QString &id) { return id == "RGBA" || id == "RGBA16" || id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16"; } QPair getColorSpaceForColorType(int color_type, int color_nb_bits) { QPair r; if (color_type == PNG_COLOR_TYPE_PALETTE) { r.first = RGBAColorModelID.id(); r.second = Integer8BitsColorDepthID.id(); } else { if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { r.first = GrayAColorModelID.id(); } else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_RGB) { r.first = RGBAColorModelID.id(); } if (color_nb_bits == 16) { r.second = Integer16BitsColorDepthID.id(); } else if (color_nb_bits <= 8) { r.second = Integer8BitsColorDepthID.id(); } } return r; } void fillText(png_text* p_text, const char* key, QString& text) { p_text->compression = PNG_TEXT_COMPRESSION_zTXt; p_text->key = const_cast(key); char* textc = new char[text.length()+1]; strcpy(textc, text.toLatin1()); p_text->text = textc; p_text->text_length = text.length() + 1; } long formatStringList(char *string, const size_t length, const char *format, va_list operands) { int n = vsnprintf(string, length, format, operands); if (n < 0) string[length-1] = '\0'; return((long) n); } long formatString(char *string, const size_t length, const char *format, ...) { long n; va_list operands; va_start(operands, format); n = (long) formatStringList(string, length, format, operands); va_end(operands); return(n); } void writeRawProfile(png_struct *ping, png_info *ping_info, QString profile_type, QByteArray profile_data) { png_textp text; png_uint_32 allocated_length, description_length; const uchar hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; dbgFile << "Writing Raw profile: type=" << profile_type << ", length=" << profile_data.length() << endl; text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text)); description_length = profile_type.length(); allocated_length = (png_uint_32)(profile_data.length() * 2 + (profile_data.length() >> 5) + 20 + description_length); text[0].text = (png_charp) png_malloc(ping, allocated_length); QString key = QLatin1Literal("Raw profile type ") + profile_type.toLatin1(); QByteArray keyData = key.toLatin1(); text[0].key = keyData.data(); uchar* sp = (uchar*)profile_data.data(); png_charp dp = text[0].text; *dp++ = '\n'; memcpy(dp, profile_type.toLatin1().constData(), profile_type.length()); dp += description_length; *dp++ = '\n'; formatString(dp, allocated_length - strlen(text[0].text), "%8lu ", profile_data.length()); dp += 8; for (long i = 0; i < (long) profile_data.length(); i++) { if (i % 36 == 0) *dp++ = '\n'; *(dp++) = (char) hex[((*sp >> 4) & 0x0f)]; *(dp++) = (char) hex[((*sp++) & 0x0f)]; } *dp++ = '\n'; *dp = '\0'; text[0].text_length = (png_size_t)(dp - text[0].text); text[0].compression = -1; if (text[0].text_length <= allocated_length) png_set_text(ping, ping_info, text, 1); png_free(ping, text[0].text); png_free(ping, text); } QByteArray png_read_raw_profile(png_textp text) { QByteArray profile; static const unsigned char unhex[103] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15 }; png_charp sp = text[0].text + 1; /* look for newline */ while (*sp != '\n') sp++; /* look for length */ while (*sp == '\0' || *sp == ' ' || *sp == '\n') sp++; png_uint_32 length = (png_uint_32) atol(sp); while (*sp != ' ' && *sp != '\n') sp++; if (length == 0) { return profile; } profile.resize(length); /* copy profile, skipping white space and column 1 "=" signs */ unsigned char *dp = (unsigned char*)profile.data(); png_uint_32 nibbles = length * 2; for (png_uint_32 i = 0; i < nibbles; i++) { while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') { if (*sp == '\0') { return QByteArray(); } sp++; } if (i % 2 == 0) *dp = (unsigned char)(16 * unhex[(int) *sp++]); else (*dp++) += unhex[(int) *sp++]; } return profile; } void decode_meta_data(png_textp text, KisMetaData::Store* store, QString type, int headerSize) { dbgFile << "Decoding " << type << " " << text[0].key; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value(type); Q_ASSERT(exifIO); QByteArray rawProfile = png_read_raw_profile(text); if (headerSize > 0) { rawProfile.remove(0, headerSize); } if (rawProfile.size() > 0) { QBuffer buffer; buffer.setData(rawProfile); exifIO->loadFrom(store, &buffer); } else { dbgFile << "Decoding failed"; } } } KisPNGConverter::KisPNGConverter(KisDocument *doc, bool batchMode) { // Q_ASSERT(doc); // Q_ASSERT(adapter); m_doc = doc; m_stop = false; m_max_row = 0; m_image = 0; m_batchMode = batchMode; } KisPNGConverter::~KisPNGConverter() { } class KisPNGReadStream { public: KisPNGReadStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) { } int nextValue() { if (m_posinc == 0) { m_posinc = 8; m_buf++; } m_posinc -= m_depth; return (((*m_buf) >> (m_posinc)) & ((1 << m_depth) - 1)); } private: quint32 m_posinc, m_depth; quint8* m_buf; }; class KisPNGWriteStream { public: KisPNGWriteStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) { *m_buf = 0; } void setNextValue(int v) { if (m_posinc == 0) { m_posinc = 8; m_buf++; *m_buf = 0; } m_posinc -= m_depth; *m_buf = (v << m_posinc) | *m_buf; } private: quint32 m_posinc, m_depth; quint8* m_buf; }; class KisPNGReaderAbstract { public: KisPNGReaderAbstract(png_structp _png_ptr, int _width, int _height) : png_ptr(_png_ptr), width(_width), height(_height) {} virtual ~KisPNGReaderAbstract() {} virtual png_bytep readLine() = 0; protected: png_structp png_ptr; int width, height; }; class KisPNGReaderLineByLine : public KisPNGReaderAbstract { public: KisPNGReaderLineByLine(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height) { png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr); row_pointer = new png_byte[rowbytes]; } ~KisPNGReaderLineByLine() override { delete[] row_pointer; } png_bytep readLine() override { png_read_row(png_ptr, row_pointer, 0); return row_pointer; } private: png_bytep row_pointer; }; class KisPNGReaderFullImage : public KisPNGReaderAbstract { public: KisPNGReaderFullImage(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height), y(0) { row_pointers = new png_bytep[height]; png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr); for (int i = 0; i < height; i++) { row_pointers[i] = new png_byte[rowbytes]; } png_read_image(png_ptr, row_pointers); } ~KisPNGReaderFullImage() override { for (int i = 0; i < height; i++) { delete[] row_pointers[i]; } delete[] row_pointers; } png_bytep readLine() override { return row_pointers[y++]; } private: png_bytepp row_pointers; int y; }; static void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length) { QIODevice *in = (QIODevice *)png_get_io_ptr(png_ptr); while (length) { int nr = in->read((char*)data, length); if (nr <= 0) { png_error(png_ptr, "Read Error"); return; } length -= nr; } } static void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length) { QIODevice* out = (QIODevice*)png_get_io_ptr(png_ptr); uint nr = out->write((char*)data, length); if (nr != length) { png_error(png_ptr, "Write Error"); return; } } static void _flush_fn(png_structp png_ptr) { Q_UNUSED(png_ptr); } -KisImageBuilder_Result KisPNGConverter::buildImage(QIODevice* iod) +KisImportExportErrorCode KisPNGConverter::buildImage(QIODevice* iod) { dbgFile << "Start decoding PNG File"; png_byte signature[8]; iod->peek((char*)signature, 8); #if PNG_LIBPNG_VER < 10400 if (!png_check_sig(signature, 8)) { #else if (png_sig_cmp(signature, 0, 8) != 0) { #endif iod->close(); - return (KisImageBuilder_RESULT_BAD_FETCH); + return (ImportExportCodes::FileFormatIncorrect); } // Initialize the internal structures png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_ptr) { iod->close(); } png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0); iod->close(); - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } png_infop end_info = png_create_info_struct(png_ptr); if (!end_info) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0); iod->close(); - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } // Catch errors if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); iod->close(); - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } // Initialize the special png_set_read_fn(png_ptr, iod, _read_fn); #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif // read all PNG info up to image data png_read_info(png_ptr, info_ptr); if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) { png_set_expand(png_ptr); } if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE && png_get_bit_depth(png_ptr, info_ptr) < 8) { png_set_packing(png_ptr); } if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE && (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) { png_set_expand(png_ptr); } png_read_update_info(png_ptr, info_ptr); // Read information about the png png_uint_32 width, height; int color_nb_bits, color_type, interlace_type; png_get_IHDR(png_ptr, info_ptr, &width, &height, &color_nb_bits, &color_type, &interlace_type, 0, 0); dbgFile << "width = " << width << " height = " << height << " color_nb_bits = " << color_nb_bits << " color_type = " << color_type << " interlace_type = " << interlace_type << endl; // swap byteorder on little endian machines. #ifndef WORDS_BIGENDIAN if (color_nb_bits > 8) png_set_swap(png_ptr); #endif // Determine the colorspace QPair csName = getColorSpaceForColorType(color_type, color_nb_bits); if (csName.first.isEmpty()) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); iod->close(); - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } bool hasalpha = (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA); // Read image profile png_charp profile_name; #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 png_bytep profile_data; #else png_charp profile_data; #endif int compression_type; png_uint_32 proflen; // Get the various optional chunks // https://www.w3.org/TR/PNG/#11cHRM #if defined(PNG_cHRM_SUPPORTED) double whitePointX, whitePointY; double redX, redY; double greenX, greenY; double blueX, blueY; png_get_cHRM(png_ptr,info_ptr, &whitePointX, &whitePointY, &redX, &redY, &greenX, &greenY, &blueX, &blueY); dbgFile << "cHRM:" << whitePointX << whitePointY << redX << redY << greenX << greenY << blueX << blueY; #endif // https://www.w3.org/TR/PNG/#11gAMA #if defined(PNG_GAMMA_SUPPORTED) double gamma; png_get_gAMA(png_ptr, info_ptr, &gamma); dbgFile << "gAMA" << gamma; #endif // https://www.w3.org/TR/PNG/#11sRGB #if defined(PNG_sRGB_SUPPORTED) int sRGBIntent; png_get_sRGB(png_ptr, info_ptr, &sRGBIntent); dbgFile << "sRGB" << sRGBIntent; #endif bool fromBlender = false; png_text* text_ptr; int num_comments; png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments); for (int i = 0; i < num_comments; i++) { QString key = QString(text_ptr[i].key).toLower(); if (key == "file") { QString relatedFile = text_ptr[i].text; if (relatedFile.contains(".blend", Qt::CaseInsensitive)){ fromBlender=true; } } } bool loadedImageIsHDR = false; const KoColorProfile* profile = 0; if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) { QByteArray profile_rawdata; // XXX: Hardcoded for icc type -- is that correct for us? profile_rawdata.resize(proflen); memcpy(profile_rawdata.data(), profile_data, proflen); profile = KoColorSpaceRegistry::instance()->createColorProfile(csName.first, csName.second, profile_rawdata); Q_CHECK_PTR(profile); if (profile) { // dbgFile << "profile name: " << profile->productName() << " profile description: " << profile->productDescription() << " information sur le produit: " << profile->productInfo(); if (!profile->isSuitableForOutput()) { dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user } } loadedImageIsHDR = strcmp(profile_name, "ITUR_2100_PQ_FULL") == 0; } else { dbgFile << "no embedded profile, will use the default profile"; if (color_nb_bits == 16 && !fromBlender && !qAppName().toLower().contains("test") && !m_batchMode) { KisConfig cfg(true); quint32 behaviour = cfg.pasteBehaviour(); if (behaviour == PASTE_ASK) { KisDlgPngImport dlg(m_path, csName.first, csName.second); KisCursorOverrideHijacker hijacker; Q_UNUSED(hijacker); dlg.exec(); if (!dlg.profile().isEmpty()) { profile = KoColorSpaceRegistry::instance()->profileByName(dlg.profile()); } } } dbgFile << "no embedded profile, will use the default profile"; } const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second); // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << csName.first << " " << csName.second; profile = 0; } // Retrieve a pointer to the colorspace KoColorConversionTransformation* transform = 0; const KoColorSpace* cs = 0; if (loadedImageIsHDR && csName.first == RGBAColorModelID.id() && csName.second == Integer16BitsColorDepthID.id()) { const KoColorSpace *p2020PQCS = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020PQProfile()); cs = p2020PQCS; } else if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile: " << profile->name() << "\n"; cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile); } else { if (csName.first == RGBAColorModelID.id()) { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "sRGB-elle-V2-srgbtrc.icc"); } else if (csName.first == GrayAColorModelID.id()) { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "Gray-D50-elle-V2-srgbtrc.icc"); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, 0); } //TODO: two fixes : one tell the user about the problem and ask for a solution, and two once the kocolorspace include KoColorTransformation, use that instead of hacking a lcms transformation // Create the cmsTransform if needed if (profile) { transform = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } } if (cs == 0) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } // Creating the KisImageSP if (m_image == 0) { KisUndoStore *store = m_doc ? m_doc->createUndoStore() : new KisSurrogateUndoStore(); m_image = new KisImage(store, width, height, cs, "built image"); } // Read resolution int unit_type; png_uint_32 x_resolution, y_resolution; png_get_pHYs(png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type); if (x_resolution > 0 && y_resolution > 0 && unit_type == PNG_RESOLUTION_METER) { m_image->setResolution((double) POINT_TO_CM(x_resolution) / 100.0, (double) POINT_TO_CM(y_resolution) / 100.0); // It is the "invert" macro because we convert from pointer-per-inchs to points } double coeff = quint8_MAX / (double)(pow((double)2, color_nb_bits) - 1); KisPaintLayerSP layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), UCHAR_MAX); // Read comments/texts... png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments); if (m_doc) { KoDocumentInfo * info = m_doc->documentInfo(); dbgFile << "There are " << num_comments << " comments in the text"; for (int i = 0; i < num_comments; i++) { QString key = QString(text_ptr[i].key).toLower(); dbgFile << "key: " << text_ptr[i].key << ", containing: " << text_ptr[i].text << ": " << (key == "raw profile type exif " ? "isExif" : "something else"); if (key == "title") { info->setAboutInfo("title", text_ptr[i].text); } else if (key == "description") { info->setAboutInfo("comment", text_ptr[i].text); } else if (key == "author") { info->setAuthorInfo("creator", text_ptr[i].text); } else if (key.contains("raw profile type exif")) { decode_meta_data(text_ptr + i, layer->metaData(), "exif", 6); } else if (key.contains("raw profile type iptc")) { decode_meta_data(text_ptr + i, layer->metaData(), "iptc", 14); } else if (key.contains("raw profile type xmp")) { decode_meta_data(text_ptr + i, layer->metaData(), "xmp", 0); } else if (key == "version") { m_image->addAnnotation(new KisAnnotation("kpp_version", "version", QByteArray(text_ptr[i].text))); } else if (key == "preset") { m_image->addAnnotation(new KisAnnotation("kpp_preset", "preset", QByteArray(text_ptr[i].text))); } } } // Read image data QScopedPointer reader; try { if (interlace_type == PNG_INTERLACE_ADAM7) { reader.reset(new KisPNGReaderFullImage(png_ptr, info_ptr, width, height)); } else { reader.reset(new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height)); } } catch (const std::bad_alloc& e) { // new png_byte[] may raise such an exception if the image // is invalid / to large. dbgFile << "bad alloc: " << e.what(); // Free only the already allocated png_byte instances. png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } // Read the palette if the file is indexed png_colorp palette ; int num_palette; if (color_type == PNG_COLOR_TYPE_PALETTE) { png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); } // Read the transparency palette quint8 palette_alpha[256]; memset(palette_alpha, 255, 256); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { if (color_type == PNG_COLOR_TYPE_PALETTE) { png_bytep alpha_ptr; int num_alpha; png_get_tRNS(png_ptr, info_ptr, &alpha_ptr, &num_alpha, 0); for (int i = 0; i < num_alpha; ++i) { palette_alpha[i] = alpha_ptr[i]; } } } for (png_uint_32 y = 0; y < height; y++) { KisHLineIteratorSP it = layer -> paintDevice() -> createHLineIteratorNG(0, y, width); png_bytep row_pointer = reader->readLine(); switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (color_nb_bits == 16) { quint16 *src = reinterpret_cast(row_pointer); do { quint16 *d = reinterpret_cast(it->rawData()); d[0] = *(src++); if (hasalpha) { d[1] = *(src++); } else { d[1] = quint16_MAX; } if (transform) transform->transformInPlace(reinterpret_cast(d), reinterpret_cast(d), 1); } while (it->nextPixel()); } else { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); d[0] = (quint8)(stream.nextValue() * coeff); if (hasalpha) { d[1] = (quint8)(stream.nextValue() * coeff); } else { d[1] = UCHAR_MAX; } if (transform) transform->transformInPlace(d, d, 1); } while (it->nextPixel()); } // FIXME:should be able to read 1 and 4 bits depth and scale them to 8 bits" break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: if (color_nb_bits == 16) { quint16 *src = reinterpret_cast(row_pointer); do { quint16 *d = reinterpret_cast(it->rawData()); d[2] = *(src++); d[1] = *(src++); d[0] = *(src++); if (hasalpha) d[3] = *(src++); else d[3] = quint16_MAX; if (transform) transform->transformInPlace(reinterpret_cast(d), reinterpret_cast(d), 1); } while (it->nextPixel()); } else { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); d[2] = (quint8)(stream.nextValue() * coeff); d[1] = (quint8)(stream.nextValue() * coeff); d[0] = (quint8)(stream.nextValue() * coeff); if (hasalpha) d[3] = (quint8)(stream.nextValue() * coeff); else d[3] = UCHAR_MAX; if (transform) transform->transformInPlace(d, d, 1); } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_PALETTE: { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); quint8 index = stream.nextValue(); quint8 alpha = palette_alpha[ index ]; if (alpha == 0) { memset(d, 0, 4); } else { png_color c = palette[ index ]; d[2] = c.red; d[1] = c.green; d[0] = c.blue; d[3] = alpha; } } while (it->nextPixel()); } break; default: - return KisImageBuilder_RESULT_UNSUPPORTED; + return ImportExportCodes::FormatFeaturesUnsupported; } } m_image->addNode(layer.data(), m_image->rootLayer().data()); png_read_end(png_ptr, end_info); iod->close(); // Freeing memory png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -KisImageBuilder_Result KisPNGConverter::buildImage(const QString &filename) +KisImportExportErrorCode KisPNGConverter::buildImage(const QString &filename) { m_path = filename; QFile fp(filename); if (fp.exists()) { if (!fp.open(QIODevice::ReadOnly)) { dbgFile << "Failed to open PNG File"; - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::FileFormatIncorrect); } return buildImage(&fp); } - return (KisImageBuilder_RESULT_NOT_EXIST); + return (ImportExportCodes::FileNotExist); } KisImageSP KisPNGConverter::image() { return m_image; } bool KisPNGConverter::saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData) { if (store->open(filename)) { KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { dbgFile << "Could not open for writing:" << filename; return false; } KisPNGConverter pngconv(0); vKisAnnotationSP_it annotIt = 0; KisMetaData::Store* metaDataStore = 0; if (metaData) { metaDataStore = new KisMetaData::Store(*metaData); } KisPNGOptions options; options.compression = 0; options.interlace = false; options.tryToSaveAsIndexed = false; options.alpha = true; options.saveSRGBProfile = false; if (dev->colorSpace()->id() != "RGBA") { dev = new KisPaintDevice(*dev.data()); dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); } - bool success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore); - if (success != KisImageBuilder_RESULT_OK) { + KisImportExportErrorCode success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore); + if (!success.isOk()) { dbgFile << "Saving PNG failed:" << filename; delete metaDataStore; return false; } delete metaDataStore; io.close(); if (!store->close()) { return false; } } else { dbgFile << "Opening of data file failed :" << filename; return false; } return true; } -KisImageBuilder_Result KisPNGConverter::buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) +KisImportExportErrorCode KisPNGConverter::buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) { dbgFile << "Start writing PNG File " << filename; // Open a QIODevice for writing QFile fp (filename); if (!fp.open(QIODevice::WriteOnly)) { dbgFile << "Failed to open PNG File for writing"; - return (KisImageBuilder_RESULT_FAILURE); + return (KisImportExportErrorCannotWrite(fp.error())); } - KisImageBuilder_Result result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData); + KisImportExportErrorCode result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData); return result; } -KisImageBuilder_Result KisPNGConverter::buildFile(QIODevice* iodevice, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) +KisImportExportErrorCode KisPNGConverter::buildFile(QIODevice* iodevice, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) { - if (!device) - return KisImageBuilder_RESULT_INVALID_ARG; + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(device, ImportExportCodes::InternalError); if (!options.alpha) { KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace()); KoColor c(options.transparencyFillColor, device->colorSpace()); tmp->fill(imageRect, c); KisPainter gc(tmp); gc.bitBlt(imageRect.topLeft(), device, imageRect); gc.end(); device = tmp; } if (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID || device->colorSpace()->colorDepthId() == Float32BitsColorDepthID || device->colorSpace()->colorDepthId() == Float64BitsColorDepthID || options.saveAsHDR) { const KoColorSpace *dstCS = KoColorSpaceRegistry::instance()->colorSpace( device->colorSpace()->colorModelId().id(), Integer16BitsColorDepthID.id(), device->colorSpace()->profile()); if (options.saveAsHDR) { dstCS = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020PQProfile()); } KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace()); tmp->makeCloneFromRough(device, imageRect); tmp->convertTo(dstCS); device = tmp; } KIS_SAFE_ASSERT_RECOVER(!options.saveAsHDR || !options.forceSRGB) { options.forceSRGB = false; } KIS_SAFE_ASSERT_RECOVER(!options.saveAsHDR || !options.tryToSaveAsIndexed) { options.tryToSaveAsIndexed = false; } QStringList colormodels = QStringList() << RGBAColorModelID.id() << GrayAColorModelID.id(); if (options.forceSRGB || !colormodels.contains(device->colorSpace()->colorModelId().id())) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), device->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)"); device = new KisPaintDevice(*device); device->convertTo(cs); } // Initialize structures png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_ptr) { - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED png_set_check_for_invalid_index(png_ptr, 0); #endif png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp)0); - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } // If an error occurs during writing, libpng will jump here if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } // Initialize the writing // png_init_io(png_ptr, fp); // Setup the progress function // XXX: Implement progress updating -- png_set_write_status_fn(png_ptr, progress);" // setProgressTotalSteps(100/*height*/); /* set the zlib compression level */ png_set_compression_level(png_ptr, options.compression); png_set_write_fn(png_ptr, (void*)iodevice, _write_fn, _flush_fn); /* set other zlib parameters */ png_set_compression_mem_level(png_ptr, 8); png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY); png_set_compression_window_bits(png_ptr, 15); png_set_compression_method(png_ptr, 8); png_set_compression_buffer_size(png_ptr, 8192); int color_nb_bits = 8 * device->pixelSize() / device->channelCount(); int color_type = getColorTypeforColorSpace(device->colorSpace(), options.alpha); Q_ASSERT(color_type > -1); // Try to compute a table of color if the colorspace is RGB8f QScopedArrayPointer palette; int num_palette = 0; if (!options.alpha && options.tryToSaveAsIndexed && KoID(device->colorSpace()->id()) == KoID("RGBA")) { // png doesn't handle indexed images and alpha, and only have indexed for RGB8 palette.reset(new png_color[255]); KisSequentialIterator it(device, imageRect); bool toomuchcolor = false; while (it.nextPixel()) { const quint8* c = it.oldRawData(); bool findit = false; for (int i = 0; i < num_palette; i++) { if (palette[i].red == c[2] && palette[i].green == c[1] && palette[i].blue == c[0]) { findit = true; break; } } if (!findit) { if (num_palette == 255) { toomuchcolor = true; break; } palette[num_palette].red = c[2]; palette[num_palette].green = c[1]; palette[num_palette].blue = c[0]; num_palette++; } } if (!toomuchcolor) { dbgFile << "Found a palette of " << num_palette << " colors"; color_type = PNG_COLOR_TYPE_PALETTE; if (num_palette <= 2) { color_nb_bits = 1; } else if (num_palette <= 4) { color_nb_bits = 2; } else if (num_palette <= 16) { color_nb_bits = 4; } else { color_nb_bits = 8; } } else { palette.reset(); } } int interlacetype = options.interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(color_type >= 0, KisImageBuilder_RESULT_FAILURE); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(color_type >= 0, ImportExportCodes::Failure); png_set_IHDR(png_ptr, info_ptr, imageRect.width(), imageRect.height(), color_nb_bits, color_type, interlacetype, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // set sRGB only if the profile is sRGB -- http://www.w3.org/TR/PNG/#11sRGB says sRGB and iCCP should not both be present bool sRGB = device->colorSpace()->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive); /* * This automatically writes the correct gamma and chroma chunks along with the sRGB chunk, but firefox's * color management is bugged, so once you give it any incentive to start color managing an sRGB image it * will turn, for example, a nice desaturated rusty red into bright poppy red. So this is disabled for now. */ /*if (!options.saveSRGBProfile && sRGB) { png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL); }*/ /** TODO: Firefox still opens the image incorrectly if there is gAMA+cHRM tags * present. According to the standard it should use iCCP tag with higher priority, * but it doesn't: * * "When the iCCP chunk is present, PNG decoders that recognize it and are capable * of colour management [ICC] shall ignore the gAMA and cHRM chunks and use * the iCCP chunk instead and interpret it according to [ICC-1] and [ICC-1A]" */ #if 0 if (options.saveAsHDR) { // https://www.w3.org/TR/PNG/#11gAMA #if defined(PNG_GAMMA_SUPPORTED) // the values are set in accurdance of HDR-PNG standard: // https://www.w3.org/TR/png-hdr-pq/ png_set_gAMA_fixed(png_ptr, info_ptr, 15000); dbgFile << "gAMA" << "(Rec 2100)"; #endif #if defined PNG_cHRM_SUPPORTED png_set_cHRM_fixed(png_ptr, info_ptr, 31270, 32900, // white point 70800, 29200, // red 17000, 79700, // green 13100, 4600 // blue ); dbgFile << "cHRM" << "(Rec 2100)"; #endif } #endif // we should ensure we don't access non-existing palette object - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(palette || color_type != PNG_COLOR_TYPE_PALETTE, KisImageBuilder_RESULT_FAILURE); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(palette || color_type != PNG_COLOR_TYPE_PALETTE, ImportExportCodes::Failure); // set the palette if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_PLTE(png_ptr, info_ptr, palette.data(), num_palette); } // Save annotation vKisAnnotationSP_it it = annotationsStart; while (it != annotationsEnd) { if (!(*it) || (*it)->type().isEmpty()) { dbgFile << "Warning: empty annotation"; it++; continue; } dbgFile << "Trying to store annotation of type " << (*it) -> type() << " of size " << (*it) -> annotation() . size(); if ((*it) -> type().startsWith(QString("krita_attribute:"))) { // // Attribute // XXX: it should be possible to save krita_attributes in the \"CHUNKs\"" dbgFile << "cannot save this annotation : " << (*it) -> type(); } else if ((*it)->type() == "kpp_version" || (*it)->type() == "kpp_preset" ) { dbgFile << "Saving preset information " << (*it)->description(); png_textp text = (png_textp) png_malloc(png_ptr, (png_uint_32) sizeof(png_text)); QByteArray keyData = (*it)->description().toLatin1(); text[0].key = keyData.data(); text[0].text = (char*)(*it)->annotation().data(); text[0].text_length = (*it)->annotation().size(); text[0].compression = -1; png_set_text(png_ptr, info_ptr, text, 1); png_free(png_ptr, text); } it++; } // Save the color profile const KoColorProfile* colorProfile = device->colorSpace()->profile(); QByteArray colorProfileData = colorProfile->rawData(); if (!sRGB || options.saveSRGBProfile) { #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 const char *typeString = !options.saveAsHDR ? "icc" : "ITUR_2100_PQ_FULL"; png_set_iCCP(png_ptr, info_ptr, (png_const_charp)typeString, PNG_COMPRESSION_TYPE_BASE, (png_const_bytep)colorProfileData.constData(), colorProfileData . size()); #else // older version of libpng has a problem with constness on the parameters char typeStringICC[] = "icc"; char typeStringHDR[] = "ITUR_2100_PQ_FULL"; char *typeString = !options.saveAsHDR ? typeStringICC : typeStringHDR; png_set_iCCP(png_ptr, info_ptr, typeString, PNG_COMPRESSION_TYPE_BASE, colorProfileData.data(), colorProfileData . size()); #endif } // save comments from the document information // warning: according to the official png spec, the keys need to be capitalized! if (m_doc) { png_text texts[4]; int nbtexts = 0; KoDocumentInfo * info = m_doc->documentInfo(); QString title = info->aboutInfo("title"); if (!title.isEmpty() && options.storeMetaData) { fillText(texts + nbtexts, "Title", title); nbtexts++; } QString abstract = info->aboutInfo("subject"); if (abstract.isEmpty()) { abstract = info->aboutInfo("abstract"); } if (!abstract.isEmpty() && options.storeMetaData) { QString keywords = info->aboutInfo("keyword"); if (!keywords.isEmpty()) { abstract = abstract + " keywords: " + keywords; } fillText(texts + nbtexts, "Description", abstract); nbtexts++; } QString license = info->aboutInfo("license"); if (!license.isEmpty() && options.storeMetaData) { fillText(texts + nbtexts, "Copyright", license); nbtexts++; } QString author = info->authorInfo("creator"); if (!author.isEmpty() && options.storeAuthor) { if (!info->authorContactInfo().isEmpty()) { QString contact = info->authorContactInfo().at(0); if (!contact.isEmpty()) { author = author+"("+contact+")"; } } fillText(texts + nbtexts, "Author", author); nbtexts++; } png_set_text(png_ptr, info_ptr, texts, nbtexts); } // Save metadata following imagemagick way // Save exif if (metaData && !metaData->empty()) { if (options.exif) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QBuffer buffer; exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); writeRawProfile(png_ptr, info_ptr, "exif", buffer.data()); } // Save IPTC if (options.iptc) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); QBuffer buffer; iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "IPTC information size is" << buffer.data().size(); writeRawProfile(png_ptr, info_ptr, "iptc", buffer.data()); } // Save XMP if (options.xmp) { dbgFile << "Trying to save XMP information"; KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); QBuffer buffer; xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::NoHeader); dbgFile << "XMP information size is" << buffer.data().size(); writeRawProfile(png_ptr, info_ptr, "xmp", buffer.data()); } } #if 0 // Unimplemented? // Save resolution int unit_type; png_uint_32 x_resolution, y_resolution; #endif png_set_pHYs(png_ptr, info_ptr, CM_TO_POINT(xRes) * 100.0, CM_TO_POINT(yRes) * 100.0, PNG_RESOLUTION_METER); // It is the "invert" macro because we convert from pointer-per-inchs to points // Save the information to the file png_write_info(png_ptr, info_ptr); png_write_flush(png_ptr); // swap byteorder on little endian machines. #ifndef WORDS_BIGENDIAN if (color_nb_bits > 8) png_set_swap(png_ptr); #endif // Write the PNG // png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0); struct RowPointersStruct { RowPointersStruct(const QSize &size, int pixelSize) : numRows(size.height()) { rows = new png_byte*[numRows]; for (int i = 0; i < numRows; i++) { rows[i] = new png_byte[size.width() * pixelSize]; } } ~RowPointersStruct() { for (int i = 0; i < numRows; i++) { delete[] rows[i]; } delete[] rows; } const int numRows = 0; png_byte** rows = 0; }; // Fill the data structure RowPointersStruct rowPointers(imageRect.size(), device->pixelSize()); int row = 0; for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++, row++) { KisHLineConstIteratorSP it = device->createHLineConstIteratorNG(imageRect.x(), y, imageRect.width()); switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (color_nb_bits == 16) { quint16 *dst = reinterpret_cast(rowPointers.rows[row]); do { const quint16 *d = reinterpret_cast(it->oldRawData()); *(dst++) = d[0]; if (options.alpha) *(dst++) = d[1]; } while (it->nextPixel()); } else { quint8 *dst = rowPointers.rows[row]; do { const quint8 *d = it->oldRawData(); *(dst++) = d[0]; if (options.alpha) *(dst++) = d[1]; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: if (color_nb_bits == 16) { quint16 *dst = reinterpret_cast(rowPointers.rows[row]); do { const quint16 *d = reinterpret_cast(it->oldRawData()); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; if (options.alpha) *(dst++) = d[3]; } while (it->nextPixel()); } else { quint8 *dst = rowPointers.rows[row]; do { const quint8 *d = it->oldRawData(); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; if (options.alpha) *(dst++) = d[3]; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_PALETTE: { quint8 *dst = rowPointers.rows[row]; KisPNGWriteStream writestream(dst, color_nb_bits); do { const quint8 *d = it->oldRawData(); int i; for (i = 0; i < num_palette; i++) { if (palette[i].red == d[2] && palette[i].green == d[1] && palette[i].blue == d[0]) { break; } } writestream.setNextValue(i); } while (it->nextPixel()); } break; default: - return KisImageBuilder_RESULT_UNSUPPORTED; + return ImportExportCodes::FormatColorSpaceUnsupported; } } png_write_image(png_ptr, rowPointers.rows); // Writing is over png_write_end(png_ptr, info_ptr); // Free memory png_destroy_write_struct(&png_ptr, &info_ptr); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } void KisPNGConverter::cancel() { m_stop = true; } void KisPNGConverter::progress(png_structp png_ptr, png_uint_32 row_number, int pass) { if (png_ptr == 0 || row_number > PNG_MAX_UINT || pass > 7) return; // setProgress(row_number); } bool KisPNGConverter::isColorSpaceSupported(const KoColorSpace *cs) { return colorSpaceIdSupported(cs->id()); } diff --git a/libs/ui/kis_png_converter.h b/libs/ui/kis_png_converter.h index 0842928991..e6bb466631 100644 --- a/libs/ui/kis_png_converter.h +++ b/libs/ui/kis_png_converter.h @@ -1,146 +1,146 @@ /* * Copyright (c) 2005, 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_PNG_CONVERTER_H_ #define _KIS_PNG_CONVERTER_H_ #include #include #include #include "kis_types.h" #include "kis_global.h" #include "kis_annotation.h" #include -#include +#include class KoStore; class KisDocument; class KoColorSpace; namespace KisMetaData { class Filter; class Store; } struct KisPNGOptions { KisPNGOptions() : compression(0) , interlace(false) , alpha(true) , exif(true) , iptc(true) , xmp(true) , tryToSaveAsIndexed(true) , saveSRGBProfile(false) , forceSRGB(false) , storeMetaData(false) , storeAuthor(false) , saveAsHDR(false) , transparencyFillColor(Qt::white) {} int compression; bool interlace; bool alpha; bool exif; bool iptc; bool xmp; bool tryToSaveAsIndexed; bool saveSRGBProfile; bool forceSRGB; bool storeMetaData; bool storeAuthor; bool saveAsHDR; QList filters; QColor transparencyFillColor; }; /** * This class allows to import/export a PNG from either a file or a QIODevice. */ // XXX_PROGRESS (pass KoUpdater to the png converter) class KRITAUI_EXPORT KisPNGConverter : public QObject { Q_OBJECT public: /** * Initialize the converter. * @param doc the KisDocument related to the image, can be null if you don't have a KisDocument * @param batchMode whether to use the batch mode */ KisPNGConverter(KisDocument *doc, bool batchMode = false); ~KisPNGConverter() override; public: /** * Load an image from an URL. If the image is not on a local drive, the image is first downloaded to a * temporary location. * @param filename the file name of the image */ - KisImageBuilder_Result buildImage(const QString &filename); + KisImportExportErrorCode buildImage(const QString &filename); /** * Load an image from a QIODevice. * @param iod device to access the data */ - KisImageBuilder_Result buildImage(QIODevice* iod); + KisImportExportErrorCode buildImage(QIODevice* iod); /** * Save a layer to a PNG * @param filename the name of the destination file * @param imageRect the image rectangle to save * @param xRes resolution along x axis * @param yRes resolution along y axis * @param device the paint device to save * @param annotationsStart an iterator on the first annotation * @param annotationsEnd an iterator on the last annotation * @param options PNG formatting options * @param metaData image metadata */ - KisImageBuilder_Result buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData); - KisImageBuilder_Result buildFile(QIODevice*, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData); + KisImportExportErrorCode buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData); + KisImportExportErrorCode buildFile(QIODevice*, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData); /** * Retrieve the constructed image */ KisImageSP image(); /** * @brief saveDeviceToStore saves the given paint device to the KoStore. If the device is not 8 bits sRGB, it will be converted to 8 bits sRGB. * @return true if the saving succeeds */ static bool saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData = 0); static bool isColorSpaceSupported(const KoColorSpace *cs); public Q_SLOTS: virtual void cancel(); private: void progress(png_structp png_ptr, png_uint_32 row_number, int pass); private: png_uint_32 m_max_row; KisImageSP m_image; KisDocument *m_doc; bool m_stop; bool m_batchMode; QString m_path; }; #endif diff --git a/libs/ui/kis_popup_palette.cpp b/libs/ui/kis_popup_palette.cpp index d14aca9ee3..b35e7c769c 100644 --- a/libs/ui/kis_popup_palette.cpp +++ b/libs/ui/kis_popup_palette.cpp @@ -1,994 +1,987 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp Copyright 2016 Scott Petrovic This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_canvas2.h" #include "kis_config.h" #include "kis_popup_palette.h" #include "kis_favorite_resource_manager.h" #include "kis_icon_utils.h" #include "KisResourceServerProvider.h" #include #include #include #include #include #include #include #include #include #include "kis_signal_compressor.h" #include "brushhud/kis_brush_hud.h" #include "brushhud/kis_round_hud_button.h" #include "kis_signals_blocker.h" #include "kis_canvas_controller.h" #include "kis_acyclic_signal_connector.h" - +#include "KisMouseClickEater.h" class PopupColorTriangle : public KoTriangleColorSelector { public: PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget* parent) : KoTriangleColorSelector(displayRenderer, parent) - , m_dragging(false) { + , m_dragging(false) + { } ~PopupColorTriangle() override {} void tabletEvent(QTabletEvent* event) override { event->accept(); QMouseEvent* mouseEvent = 0; // this will tell the pop-up palette widget to close if(event->button() == Qt::RightButton) { emit requestCloseContainer(); } // ignore any tablet events that are done with the right click // Tablet move events don't return a "button", so catch that too if(event->button() == Qt::LeftButton || event->type() == QEvent::TabletMove) { switch (event->type()) { case QEvent::TabletPress: mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = true; mousePressEvent(mouseEvent); break; case QEvent::TabletMove: mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(), (m_dragging) ? Qt::LeftButton : Qt::NoButton, (m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers()); mouseMoveEvent(mouseEvent); break; case QEvent::TabletRelease: mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(), Qt::LeftButton, Qt::LeftButton, event->modifiers()); m_dragging = false; mouseReleaseEvent(mouseEvent); break; default: break; } } delete mouseEvent; } private: bool m_dragging; }; KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConverter* coordinatesConverter ,KisFavoriteResourceManager* manager, const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent) : QWidget(parent, Qt::FramelessWindowHint) , m_coordinatesConverter(coordinatesConverter) , m_viewManager(viewManager) , m_actionManager(viewManager->actionManager()) , m_resourceManager(manager) , m_displayRenderer(displayRenderer) , m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE)) , m_actionCollection(viewManager->actionCollection()) , m_acyclicConnector(new KisAcyclicSignalConnector(this)) + , m_clicksEater(new KisMouseClickEater(Qt::RightButton, 1, this)) { // some UI controls are defined and created based off these variables const int borderWidth = 3; if (KisConfig(true).readEntry("popuppalette/usevisualcolorselector", false)) { m_triangleColorSelector = new KisVisualColorSelector(this); } else { m_triangleColorSelector = new PopupColorTriangle(displayRenderer, this); } m_triangleColorSelector->setDisplayRenderer(displayRenderer); m_triangleColorSelector->setConfig(true,false); m_triangleColorSelector->move(m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth, m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth); m_triangleColorSelector->resize(m_colorHistoryInnerRadius*2-borderWidth*2, m_colorHistoryInnerRadius*2-borderWidth*2); m_triangleColorSelector->setVisible(true); KoColor fgcolor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()); if (m_resourceManager) { fgcolor = provider->fgColor(); } m_triangleColorSelector->slotSetColor(fgcolor); + + /** + * Tablet support code generates a spurious right-click right after opening + * the window, so we should ignore it. Next right-click will be used for + * closing the popup palette + */ + this->installEventFilter(m_clicksEater); + m_triangleColorSelector->installEventFilter(m_clicksEater); + QRegion maskedRegion(0, 0, m_triangleColorSelector->width(), m_triangleColorSelector->height(), QRegion::Ellipse ); m_triangleColorSelector->setMask(maskedRegion); //setAttribute(Qt::WA_TranslucentBackground, true); connect(m_triangleColorSelector, SIGNAL(sigNewColor(KoColor)), m_colorChangeCompressor.data(), SLOT(start())); connect(m_colorChangeCompressor.data(), SIGNAL(timeout()), SLOT(slotEmitColorChanged())); connect(m_triangleColorSelector, SIGNAL(requestCloseContainer()), this, SLOT(slotHide())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_triangleColorSelector, SLOT(configurationChanged())); m_acyclicConnector->connectForwardKoColor(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)), this, SLOT(slotExternalFgColorChanged(KoColor))); m_acyclicConnector->connectBackwardKoColor(this, SIGNAL(sigChangefGColor(KoColor)), m_resourceManager, SIGNAL(sigSetFGColor(KoColor))); connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int))); connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int))); connect(m_resourceManager, SIGNAL(setSelectedColor(int)), SLOT(slotSetSelectedColor(int))); connect(m_resourceManager, SIGNAL(updatePalettes()), SLOT(slotUpdate())); connect(m_resourceManager, SIGNAL(hidePalettes()), SLOT(slotHide())); // This is used to handle a bug: // If pop up palette is visible and a new colour is selected, the new colour // will be added when the user clicks on the canvas to hide the palette // In general, we want to be able to store recent color if the pop up palette // is not visible m_timer.setSingleShot(true); connect(this, SIGNAL(sigTriggerTimer()), this, SLOT(slotTriggerTimer())); connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotEnableChangeFGColor())); connect(this, SIGNAL(sigEnableChangeFGColor(bool)), m_resourceManager, SIGNAL(sigEnableChangeColor(bool))); setCursor(Qt::ArrowCursor); setMouseTracking(true); setHoveredPreset(-1); setHoveredColor(-1); setSelectedColor(-1); m_brushHud = new KisBrushHud(provider, parent); m_brushHud->setFixedHeight(int(m_popupPaletteSize)); m_brushHud->setVisible(false); const int auxButtonSize = 35; m_settingsButton = new KisRoundHudButton(this); m_settingsButton->setGeometry(m_popupPaletteSize - 2.2 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_settingsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup())); KisConfig cfg(true); m_brushHudButton = new KisRoundHudButton(this); m_brushHudButton->setCheckable(true); m_brushHudButton->setGeometry(m_popupPaletteSize - 1.0 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_brushHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool))); m_brushHudButton->setChecked(cfg.showBrushHud()); // add some stuff below the pop-up palette that will make it easier to use for tablet people QVBoxLayout* vLayout = new QVBoxLayout(this); // main layout QSpacerItem* verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding); vLayout->addSpacerItem(verticalSpacer); // this should push the box to the bottom QHBoxLayout* hLayout = new QHBoxLayout(); vLayout->addLayout(hLayout); mirrorMode = new KisHighlightedToolButton(this); mirrorMode->setFixedSize(35, 35); mirrorMode->setToolTip(i18n("Mirror Canvas")); mirrorMode->setDefaultAction(m_actionCollection->action("mirror_canvas")); canvasOnlyButton = new KisHighlightedToolButton(this); canvasOnlyButton->setFixedSize(35, 35); canvasOnlyButton->setToolTip(i18n("Canvas Only")); canvasOnlyButton->setDefaultAction(m_actionCollection->action("view_show_canvas_only")); zoomToOneHundredPercentButton = new QPushButton(this); zoomToOneHundredPercentButton->setText(i18n("100%")); zoomToOneHundredPercentButton->setFixedHeight(35); zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%")); connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked())); zoomCanvasSlider = new QSlider(Qt::Horizontal, this); zoomSliderMinValue = 10; // set in % zoomSliderMaxValue = 200; // set in % zoomCanvasSlider->setRange(zoomSliderMinValue, zoomSliderMaxValue); zoomCanvasSlider->setFixedHeight(35); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); zoomCanvasSlider->setSingleStep(1); zoomCanvasSlider->setPageStep(1); connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int))); connect(zoomCanvasSlider, SIGNAL(sliderPressed()), this, SLOT(slotZoomSliderPressed())); connect(zoomCanvasSlider, SIGNAL(sliderReleased()), this, SLOT(slotZoomSliderReleased())); slotUpdateIcons(); hLayout->addWidget(mirrorMode); hLayout->addWidget(canvasOnlyButton); hLayout->addWidget(zoomToOneHundredPercentButton); hLayout->addWidget(zoomCanvasSlider); setVisible(true); setVisible(false); opacityChange = new QGraphicsOpacityEffect(this); setGraphicsEffect(opacityChange); // Prevent tablet events from being captured by the canvas setAttribute(Qt::WA_NoMousePropagation, true); } void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color) { //hack to get around cmyk for now. if (color.colorSpace()->colorChannelCount()>3) { KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromKoColor(color); m_triangleColorSelector->slotSetColor(c); } else { m_triangleColorSelector->slotSetColor(color); } } void KisPopupPalette::slotEmitColorChanged() { if (isVisible()) { update(); emit sigChangefGColor(m_triangleColorSelector->getCurrentColor()); } } //setting KisPopupPalette properties int KisPopupPalette::hoveredPreset() const { return m_hoveredPreset; } void KisPopupPalette::setHoveredPreset(int x) { m_hoveredPreset = x; } int KisPopupPalette::hoveredColor() const { return m_hoveredColor; } void KisPopupPalette::setHoveredColor(int x) { m_hoveredColor = x; } int KisPopupPalette::selectedColor() const { return m_selectedColor; } void KisPopupPalette::setSelectedColor(int x) { m_selectedColor = x; } void KisPopupPalette::slotTriggerTimer() { m_timer.start(750); } void KisPopupPalette::slotEnableChangeFGColor() { emit sigEnableChangeFGColor(true); } void KisPopupPalette::slotZoomSliderChanged(int zoom) { emit zoomLevelChanged(zoom); } void KisPopupPalette::slotZoomSliderPressed() { m_isZoomingCanvas = true; } void KisPopupPalette::slotZoomSliderReleased() { m_isZoomingCanvas = false; } void KisPopupPalette::adjustLayout(const QPoint &p) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); if (isVisible() && parentWidget()) { float hudMargin = 30.0; const QRect fitRect = kisGrowRect(parentWidget()->rect(), -20.0); // -20 is widget margin const QPoint paletteCenterOffset(m_popupPaletteSize / 2, m_popupPaletteSize / 2); QRect paletteRect = rect(); paletteRect.moveTo(p - paletteCenterOffset); if (m_brushHudButton->isChecked()) { m_brushHud->updateGeometry(); paletteRect.adjust(0, 0, m_brushHud->width() + hudMargin, 0); } paletteRect = kisEnsureInRect(paletteRect, fitRect); move(paletteRect.topLeft()); m_brushHud->move(paletteRect.topLeft() + QPoint(m_popupPaletteSize + hudMargin, 0)); m_lastCenterPoint = p; } } void KisPopupPalette::slotUpdateIcons() { this->setPalette(qApp->palette()); for(int i=0; ichildren().size(); i++) { QWidget *w = qobject_cast(this->children().at(i)); if (w) { w->setPalette(qApp->palette()); } } zoomToOneHundredPercentButton->setIcon(m_actionCollection->action("zoom_to_100pct")->icon()); m_brushHud->updateIcons(); m_settingsButton->setIcon(KisIconUtils::loadIcon("tag")); m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right")); } void KisPopupPalette::showHudWidget(bool visible) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); const bool reallyVisible = visible && m_brushHudButton->isChecked(); if (reallyVisible) { m_brushHud->updateProperties(); } m_brushHud->setVisible(reallyVisible); adjustLayout(m_lastCenterPoint); KisConfig cfg(false); cfg.setShowBrushHud(visible); } void KisPopupPalette::showPopupPalette(const QPoint &p) { showPopupPalette(!isVisible()); adjustLayout(p); } void KisPopupPalette::showPopupPalette(bool show) { if (show) { - m_hadMousePressSinceOpening = false; - m_timeSinceOpening.start(); - - // don't set the zoom slider if we are outside of the zoom slider bounds. It will change the zoom level to within // the bounds and cause the canvas to jump between the slider's min and max if (m_coordinatesConverter->zoomInPercent() > zoomSliderMinValue && m_coordinatesConverter->zoomInPercent() < zoomSliderMaxValue ){ KisSignalsBlocker b(zoomCanvasSlider); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider } emit sigEnableChangeFGColor(!show); } else { emit sigTriggerTimer(); } setVisible(show); m_brushHud->setVisible(show && m_brushHudButton->isChecked()); } //redefinition of setVariable function to change the scope to private void KisPopupPalette::setVisible(bool b) { QWidget::setVisible(b); } void KisPopupPalette::setParent(QWidget *parent) { m_brushHud->setParent(parent); QWidget::setParent(parent); } QSize KisPopupPalette::sizeHint() const { return QSize(m_popupPaletteSize, m_popupPaletteSize + 50); // last number is the space for the toolbar below } void KisPopupPalette::resizeEvent(QResizeEvent*) { } void KisPopupPalette::paintEvent(QPaintEvent* e) { Q_UNUSED(e); QPainter painter(this); QPen pen(palette().color(QPalette::Text)); pen.setWidth(3); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::SmoothPixmapTransform); // painting background color indicator QPainterPath bgColor; bgColor.addEllipse(QPoint( 50, 80), 30, 30); painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor())); painter.drawPath(bgColor); // painting foreground color indicator QPainterPath fgColor; fgColor.addEllipse(QPoint( 60, 50), 30, 30); painter.fillPath(fgColor, m_displayRenderer->toQColor(m_triangleColorSelector->getCurrentColor())); painter.drawPath(fgColor); // create a circle background that everything else will go into QPainterPath backgroundContainer; float shrinkCircleAmount = 3;// helps the circle when the stroke is put around it QRectF circleRect(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); backgroundContainer.addEllipse( circleRect ); painter.fillPath(backgroundContainer,palette().brush(QPalette::Background)); painter.drawPath(backgroundContainer); // create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas // with the indicator QPainterPath rotationTrackPath; shrinkCircleAmount = 18; QRectF circleRect2(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); rotationTrackPath.addEllipse( circleRect2 ); pen.setWidth(1); painter.setPen(pen); painter.drawPath(rotationTrackPath); // this thing will help indicate where the starting brush preset is at. // also what direction they go to give sor order to the presets populated /* pen.setWidth(6); pen.setCapStyle(Qt::RoundCap); painter.setPen(pen); painter.drawArc(circleRect, (16*90), (16*-30)); // span angle (last parameter) is in 16th of degrees QPainterPath brushDir; brushDir.arcMoveTo(circleRect, 60); brushDir.lineTo(brushDir.currentPosition().x()-5, brushDir.currentPosition().y() - 14); painter.drawPath(brushDir); brushDir.lineTo(brushDir.currentPosition().x()-2, brushDir.currentPosition().y() + 6); painter.drawPath(brushDir); */ // the following things needs to be based off the center, so let's translate the painter painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2); // create the canvas rotation handle QPainterPath rotationIndicator = drawRotationIndicator(m_coordinatesConverter->rotationAngle(), true); painter.fillPath(rotationIndicator,palette().brush(QPalette::Text)); // hover indicator for the canvas rotation if (m_isOverCanvasRotationIndicator == true) { painter.save(); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(2); painter.setPen(pen); painter.drawPath(rotationIndicator); painter.restore(); } // create a reset canvas rotation indicator to bring the canvas back to 0 degrees QPainterPath resetRotationIndicator = drawRotationIndicator(0, false); QPen resetPen(palette().color(QPalette::Text)); resetPen.setWidth(1); painter.save(); painter.setPen(resetPen); painter.drawPath(resetRotationIndicator); painter.restore(); // painting favorite brushes QList images(m_resourceManager->favoritePresetImages()); // painting favorite brushes pixmap/icon QPainterPath presetPath; for (int pos = 0; pos < numSlots(); pos++) { painter.save(); presetPath = createPathFromPresetIndex(pos); if (pos < images.size()) { painter.setClipPath(presetPath); QRect bounds = presetPath.boundingRect().toAlignedRect(); painter.drawImage(bounds.topLeft() , images.at(pos).scaled(bounds.size() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); } else { painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it } QPen pen = painter.pen(); pen.setWidth(1); painter.setPen(pen); painter.drawPath(presetPath); painter.restore(); } if (hoveredPreset() > -1) { presetPath = createPathFromPresetIndex(hoveredPreset()); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(3); painter.setPen(pen); painter.drawPath(presetPath); } // paint recent colors area. painter.setPen(Qt::NoPen); float rotationAngle = -360.0 / m_resourceManager->recentColorsTotal(); // there might be no recent colors at the start, so paint a placeholder if (m_resourceManager->recentColorsTotal() == 0) { painter.setBrush(Qt::transparent); QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.setPen(QPen(palette().color(QPalette::Background).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); painter.drawPath(emptyRecentColorsPath); } else { for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) { QPainterPath recentColorsPath(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); //accessing recent color of index pos painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) )); painter.drawPath(recentColorsPath); painter.rotate(rotationAngle); } } // painting hovered color if (hoveredColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(hoveredColor() * -1 * rotationAngle); } } // painting selected color if (selectedColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(selectedColor() * -1 * rotationAngle); } } // if we are actively rotating the canvas or zooming, make the panel slightly transparent to see the canvas better if(m_isRotatingCanvasIndicator || m_isZoomingCanvas) { opacityChange->setOpacity(0.4); } else { opacityChange->setOpacity(1.0); } } QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius) { QPainterPath path; path.addEllipse(QPointF(x, y), outer_radius, outer_radius); path.addEllipse(QPointF(x, y), inner_radius, inner_radius); path.setFillRule(Qt::OddEvenFill); return path; } QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit) { QPainterPath path; path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit)); path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit, 360.0 / limit); path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit, - 360.0 / limit); path.closeSubpath(); return path; } QPainterPath KisPopupPalette::drawRotationIndicator(qreal rotationAngle, bool canDrag) { // used for canvas rotation. This function gets called twice. Once by the canvas rotation indicator, // and another time by the reset canvas position float canvasRotationRadians = qDegreesToRadians(rotationAngle - 90); // -90 will make 0 degrees be at the top float rotationDialXPosition = qCos(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); // m_popupPaletteSize/2 = radius float rotationDialYPosition = qSin(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); QPainterPath canvasRotationIndicator; int canvasIndicatorSize = 15; float canvasIndicatorMiddle = canvasIndicatorSize/2; QRect indicatorRectangle = QRect( rotationDialXPosition - canvasIndicatorMiddle, rotationDialYPosition - canvasIndicatorMiddle, canvasIndicatorSize, canvasIndicatorSize ); if (canDrag) { m_canvasRotationIndicatorRect = indicatorRectangle; } else { m_resetCanvasRotationIndicatorRect = indicatorRectangle; } canvasRotationIndicator.addEllipse(indicatorRectangle.x(), indicatorRectangle.y(), indicatorRectangle.width(), indicatorRectangle.height() ); return canvasRotationIndicator; } void KisPopupPalette::mouseMoveEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); setToolTip(QString()); setHoveredPreset(-1); setHoveredColor(-1); // calculate if we are over the canvas rotation knob // before we started painting, we moved the painter to the center of the widget, so the X/Y positions are offset. we need to // correct them first before looking for a click event intersection float rotationCorrectedXPos = m_canvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_canvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_canvasRotationIndicatorRect.width(), m_canvasRotationIndicatorRect.height()); if (correctedCanvasRotationIndicator.contains(point.x(), point.y())) { m_isOverCanvasRotationIndicator = true; } else { m_isOverCanvasRotationIndicator = false; } if (m_isRotatingCanvasIndicator) { // we are rotating the canvas, so calculate the rotation angle based off the center // calculate the angle we are at first QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2); float dX = point.x() - widgetCenterPoint.x(); float dY = point.y() - widgetCenterPoint.y(); float finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up float angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out KisCanvasController *canvasController = dynamic_cast(m_viewManager->canvasBase()->canvasController()); canvasController->rotateCanvas(angleDifference); emit sigUpdateCanvas(); } // don't highlight the presets if we are in the middle of rotating the canvas if (m_isRotatingCanvasIndicator == false) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); { int pos = calculatePresetIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) { setToolTip(m_resourceManager->favoritePresetList().at(pos).data()->name()); setHoveredPreset(pos); } } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { setHoveredColor(pos); } } } update(); } void KisPopupPalette::mousePressEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); - -#ifdef Q_OS_WIN - const int tableMouseEventsFlowDelay = 500; -#else - const int tableMouseEventsFlowDelay = 100; -#endif - - /** - * Tablet support code generates a spurious right-click right after opening - * the window, so we should ignore it. Next right-click will be used for - * closing the popup palette - */ - if (!m_hadMousePressSinceOpening && - m_timeSinceOpening.elapsed() > tableMouseEventsFlowDelay) { - - m_hadMousePressSinceOpening = true; - } - if (event->button() == Qt::LeftButton) { //in favorite brushes area int pos = calculateIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets() && isPointInPixmap(point, pos)) { //setSelectedBrush(pos); update(); } if (m_isOverCanvasRotationIndicator) { m_isRotatingCanvasIndicator = true; } // reset the canvas if we are over the reset canvas rotation indicator float rotationCorrectedXPos = m_resetCanvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_resetCanvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedResetCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_resetCanvasRotationIndicatorRect.width(), m_resetCanvasRotationIndicatorRect.height()); if (correctedResetCanvasRotationIndicator.contains(point.x(), point.y())) { float angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs KisCanvasController *canvasController = dynamic_cast(m_viewManager->canvasBase()->canvasController()); canvasController->rotateCanvas(angleDifference); emit sigUpdateCanvas(); } } } void KisPopupPalette::slotShowTagsPopup() { KisPaintOpPresetResourceServer *rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QStringList tags = rServer->tagNamesList(); std::sort(tags.begin(), tags.end()); if (!tags.isEmpty()) { QMenu menu; Q_FOREACH (const QString& tag, tags) { menu.addAction(tag); } QAction *action = menu.exec(QCursor::pos()); if (action) { m_resourceManager->setCurrentTag(action->text()); } } else { QWhatsThis::showText(QCursor::pos(), i18n("There are no tags available to show in this popup. To add presets, you need to tag them and then select the tag here.")); } } void KisPopupPalette::slotZoomToOneHundredPercentClicked() { QAction *action = m_actionCollection->action("zoom_to_100pct"); if (action) { action->trigger(); } // also move the zoom slider to 100% position so they are in sync zoomCanvasSlider->setValue(100); } void KisPopupPalette::tabletEvent(QTabletEvent *event) { event->ignore(); } +void KisPopupPalette::showEvent(QShowEvent *event) +{ + m_clicksEater->reset(); + QWidget::showEvent(event); +} + void KisPopupPalette::mouseReleaseEvent(QMouseEvent *event) { QPointF point = event->localPos(); event->accept(); - // see a comment in KisPopupPalette::mousePressEvent - if (m_hadMousePressSinceOpening && - event->buttons() == Qt::NoButton && + if (event->buttons() == Qt::NoButton && event->button() == Qt::RightButton) { showPopupPalette(false); return; } m_isOverCanvasRotationIndicator = false; m_isRotatingCanvasIndicator = false; if (event->button() == Qt::LeftButton) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); //in favorite brushes area if (hoveredPreset() > -1) { //setSelectedBrush(hoveredBrush()); emit sigChangeActivePaintop(hoveredPreset()); } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { emit sigUpdateRecentColor(pos); } } } } int KisPopupPalette::calculateIndex(QPointF point, int n) { calculatePresetIndex(point, n); //translate to (0,0) point.setX(point.x() - m_popupPaletteSize / 2); point.setY(point.y() - m_popupPaletteSize / 2); //rotate float smallerAngle = M_PI / 2 + M_PI / n - atan2(point.y(), point.x()); float radius = sqrt((float)point.x() * point.x() + point.y() * point.y()); point.setX(radius * cos(smallerAngle)); point.setY(radius * sin(smallerAngle)); //calculate brush index int pos = floor(acos(point.x() / radius) * n / (2 * M_PI)); if (point.y() < 0) pos = n - pos - 1; return pos; } bool KisPopupPalette::isPointInPixmap(QPointF &point, int pos) { if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) { return true; } return false; } KisPopupPalette::~KisPopupPalette() { } QPainterPath KisPopupPalette::createPathFromPresetIndex(int index) { qreal angleSlice = 360.0 / numSlots() ; // how many degrees each slice will get // the starting angle of the slice we need to draw. the negative sign makes us go clockwise. // adding 90 degrees makes us start at the top. otherwise we would start at the right qreal startingAngle = -(index * angleSlice) + 90; // the radius will get smaller as the amount of presets shown increases. 10 slots == 41 qreal radians = qDegreesToRadians((360.0/10)/2); qreal maxRadius = (m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians)))-2; radians = qDegreesToRadians(angleSlice/2); qreal presetRadius = m_colorHistoryOuterRadius * qSin(radians) / (1-qSin(radians)); //If we assume that circles will mesh like a hexagonal grid, then 3.5r is the size of two hexagons interlocking. qreal length = m_colorHistoryOuterRadius + presetRadius; // can we can fit in a second row? We don't want the preset icons to get too tiny. if (maxRadius > presetRadius) { //redo all calculations assuming a second row. if (numSlots() % 2) { angleSlice = 360.0/(numSlots()+1); startingAngle = -(index * angleSlice) + 90; } if (numSlots() != m_cachedNumSlots){ qreal tempRadius = presetRadius; qreal distance = 0; do{ tempRadius+=0.1; // Calculate the XY of two adjectant circles using this tempRadius. qreal length1 = m_colorHistoryOuterRadius + tempRadius; qreal length2 = m_colorHistoryOuterRadius + ((maxRadius*2)-tempRadius); qreal pathX1 = length1 * qCos(qDegreesToRadians(startingAngle)) - tempRadius; qreal pathY1 = -(length1) * qSin(qDegreesToRadians(startingAngle)) - tempRadius; qreal startingAngle2 = -(index+1 * angleSlice) + 90; qreal pathX2 = length2 * qCos(qDegreesToRadians(startingAngle2)) - tempRadius; qreal pathY2 = -(length2) * qSin(qDegreesToRadians(startingAngle2)) - tempRadius; // Use Pythagorean Theorem to calculate the distance between these two values. qreal m1 = pathX2-pathX1; qreal m2 = pathY2-pathY1; distance = sqrt((m1*m1)+(m2*m2)); } //As long at there's more distance than the radius of the two presets, continue increasing the radius. while((tempRadius+1)*2 < distance); m_cachedRadius = tempRadius; } m_cachedNumSlots = numSlots(); presetRadius = m_cachedRadius; length = m_colorHistoryOuterRadius + presetRadius; if (index % 2) { length = m_colorHistoryOuterRadius + ((maxRadius*2)-presetRadius); } } QPainterPath path; qreal pathX = length * qCos(qDegreesToRadians(startingAngle)) - presetRadius; qreal pathY = -(length) * qSin(qDegreesToRadians(startingAngle)) - presetRadius; qreal pathDiameter = 2 * presetRadius; // distance is used to calculate the X/Y in addition to the preset circle size path.addEllipse(pathX, pathY, pathDiameter, pathDiameter); return path; } int KisPopupPalette::calculatePresetIndex(QPointF point, int /*n*/) { for(int i = 0; i < numSlots(); i++) { QPointF adujustedPoint = point - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2); if(createPathFromPresetIndex(i).contains(adujustedPoint)) { return i; } } return -1; } int KisPopupPalette::numSlots() { KisConfig config(true); return qMax(config.favoritePresets(), 10); } diff --git a/libs/ui/kis_popup_palette.h b/libs/ui/kis_popup_palette.h index 9ef362d139..1c6962090b 100644 --- a/libs/ui/kis_popup_palette.h +++ b/libs/ui/kis_popup_palette.h @@ -1,191 +1,190 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2016 Scott Petrovic This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_POPUP_PALETTE_H #define KIS_POPUP_PALETTE_H #include #include #include #include #include "KisViewManager.h" #include "kactioncollection.h" #include "kis_tool_button.h" #include "kis_highlighted_button.h" #include class KisFavoriteResourceManager; class QWidget; class KoColor; class KoTriangleColorSelector; class KisSignalCompressor; class KisBrushHud; class KisRoundHudButton; class KisCanvasResourceProvider; class KisVisualColorSelector; class KisAcyclicSignalConnector; +class KisMouseClickEater; class KisPopupPalette : public QWidget { Q_OBJECT Q_PROPERTY(int hoveredPreset READ hoveredPreset WRITE setHoveredPreset) Q_PROPERTY(int hoveredColor READ hoveredColor WRITE setHoveredColor) Q_PROPERTY(int selectedColor READ selectedColor WRITE setSelectedColor) public: KisPopupPalette(KisViewManager*, KisCoordinatesConverter* ,KisFavoriteResourceManager*, const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent = 0); ~KisPopupPalette() override; QSize sizeHint() const override; void showPopupPalette(const QPoint&); void showPopupPalette(bool b); //functions to set up selectedBrush void setSelectedBrush(int x); int selectedBrush() const; //functions to set up selectedColor void setSelectedColor(int x); int selectedColor() const; void setParent(QWidget *parent); void tabletEvent(QTabletEvent *event) override; protected: + void showEvent(QShowEvent *event) override; void paintEvent(QPaintEvent*) override; void resizeEvent(QResizeEvent*) override; void mouseReleaseEvent(QMouseEvent*) override; void mouseMoveEvent(QMouseEvent*) override; void mousePressEvent(QMouseEvent*) override; //functions to calculate index of favorite brush or recent color in array //n is the total number of favorite brushes or recent colors int calculateIndex(QPointF, int n); int calculatePresetIndex(QPointF, int n); //functions to set up hoveredBrush void setHoveredPreset(int x); int hoveredPreset() const; //functions to set up hoveredColor void setHoveredColor(int x); int hoveredColor() const; private: void setVisible(bool b) override; QPainterPath drawDonutPathFull(int, int, int, int); QPainterPath drawDonutPathAngle(int, int, int); QPainterPath drawRotationIndicator(qreal rotationAngle, bool canDrag); bool isPointInPixmap(QPointF&, int pos); QPainterPath createPathFromPresetIndex(int index); int numSlots(); void adjustLayout(const QPoint &p); private: int m_hoveredPreset {0}; int m_hoveredColor {0}; int m_selectedColor {0}; - QElapsedTimer m_timeSinceOpening; - bool m_hadMousePressSinceOpening {false}; - - KisCoordinatesConverter *m_coordinatesConverter; KisViewManager *m_viewManager; KisActionManager *m_actionManager; KisFavoriteResourceManager *m_resourceManager; KisColorSelectorInterface *m_triangleColorSelector {0}; const KoColorDisplayRendererInterface *m_displayRenderer; QScopedPointer m_colorChangeCompressor; KActionCollection *m_actionCollection; QTimer m_timer; KisBrushHud *m_brushHud {0}; float m_popupPaletteSize {385.0}; float m_colorHistoryInnerRadius {72.0}; qreal m_colorHistoryOuterRadius {92.0}; KisRoundHudButton *m_settingsButton {0}; KisRoundHudButton *m_brushHudButton {0}; QPoint m_lastCenterPoint; QRect m_canvasRotationIndicatorRect; QRect m_resetCanvasRotationIndicatorRect; bool m_isOverCanvasRotationIndicator {false}; bool m_isRotatingCanvasIndicator {false}; bool m_isZoomingCanvas {false}; KisHighlightedToolButton *mirrorMode {0}; KisHighlightedToolButton *canvasOnlyButton {0}; QPushButton *zoomToOneHundredPercentButton {0}; QSlider *zoomCanvasSlider {0}; int zoomSliderMinValue {10}; int zoomSliderMaxValue {200}; KisAcyclicSignalConnector *m_acyclicConnector = 0; int m_cachedNumSlots {0}; qreal m_cachedRadius {0.0}; // updates the transparency and effects of the whole widget QGraphicsOpacityEffect *opacityChange {0}; + KisMouseClickEater *m_clicksEater; Q_SIGNALS: void sigChangeActivePaintop(int); void sigUpdateRecentColor(int); void sigChangefGColor(const KoColor&); void sigUpdateCanvas(); void zoomLevelChanged(int); // These are used to handle a bug: // If pop up palette is visible and a new colour is selected, the new colour // will be added when the user clicks on the canvas to hide the palette // In general, we want to be able to store recent color if the pop up palette // is not visible void sigEnableChangeFGColor(bool); void sigTriggerTimer(); public Q_SLOTS: void slotUpdateIcons(); private Q_SLOTS: void slotExternalFgColorChanged(const KoColor &color); void slotEmitColorChanged(); void slotSetSelectedColor(int x) { setSelectedColor(x); update(); } void slotTriggerTimer(); void slotEnableChangeFGColor(); void slotUpdate() { update(); } void slotHide() { showPopupPalette(false); } void slotShowTagsPopup(); void showHudWidget(bool visible); void slotZoomToOneHundredPercentClicked(); void slotZoomSliderChanged(int zoom); void slotZoomSliderPressed(); void slotZoomSliderReleased(); }; #endif // KIS_POPUP_PALETTE_H diff --git a/libs/widgets/KoTableView.cpp b/libs/widgets/KoTableView.cpp index 54cb1a49cb..a03fd0f1ba 100644 --- a/libs/widgets/KoTableView.cpp +++ b/libs/widgets/KoTableView.cpp @@ -1,93 +1,95 @@ /* * Copyright (C) 2015 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTableView.h" #include #include #include KoTableView::KoTableView(QWidget *parent) : QTableView(parent) { setSelectionMode(QAbstractItemView::SingleSelection); verticalHeader()->hide(); horizontalHeader()->hide(); verticalHeader()->setDefaultSectionSize(20); setContextMenuPolicy(Qt::DefaultContextMenu); setViewMode(FIXED_COLUMNS); QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChange(QScroller::State))); } } void KoTableView::resizeEvent(QResizeEvent *event) { QTableView::resizeEvent(event); updateView(); emit sigSizeChanged(); } void KoTableView::setViewMode(KoTableView::ViewMode mode) { m_viewMode = mode; switch (m_viewMode) { case FIXED_COLUMNS: setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Horizontal scrollbar is never needed setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); break; case FIXED_ROWS: setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Vertical scrollbar is never needed break; default: setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } } void KoTableView::updateView() { int columnCount = model()->columnCount(QModelIndex()); int rowCount = model()->rowCount(QModelIndex()); int rowHeight, columnWidth; if (m_viewMode == FIXED_COLUMNS) { columnWidth = qFloor(viewport()->size().width() / static_cast(columnCount)); for (int i = 0; i < columnCount; ++i) { setColumnWidth(i, columnWidth); } // keep aspect ratio always square. - for (int i = 0; i < rowCount; ++i) { - setRowHeight(i, columnWidth); + if (columnCount > 1) { + for (int i = 0; i < rowCount; ++i) { + setRowHeight(i, columnWidth); + } } } else if (m_viewMode == FIXED_ROWS) { if (rowCount == 0) return; // Don't divide by zero rowHeight = qFloor(viewport()->size().height() / static_cast(rowCount)); for (int i = 0; i < rowCount; ++i) { setRowHeight(i, rowHeight); } } } diff --git a/packaging/macos/default.style b/packaging/macos/default.style index f112c82fd8..8a3fb53b59 100644 --- a/packaging/macos/default.style +++ b/packaging/macos/default.style @@ -1,19 +1,19 @@ tell application "Finder" tell disk "%s" open set current view of container window to icon view set toolbar visible of container window to false set statusbar visible of container window to false - set the bounds of container window to {300, 51, 1070, 487} + set the bounds of container window to {273, 122, 1014, 484} set theViewOptions to the icon view options of container window set arrangement of theViewOptions to not arranged set icon size of theViewOptions to 80 set background picture of theViewOptions to file ".background:%s" - set position of item "krita.app" of container window to {281, 287} - set position of item "Applications" of container window to {596, 285} - set position of item "Terms of Use" of container window to {598, 132} + set position of item "krita.app" of container window to {187, 124} + set position of item "Applications" of container window to {454, 124} + set position of item "Terms of Use" of container window to {592, 124} update without registering applications delay 1 close end tell -end tell +end tell \ No newline at end of file diff --git a/packaging/macos/krita-4.1_dmgBG.jpg b/packaging/macos/krita-4.1_dmgBG.jpg deleted file mode 100644 index e7a14cd86d..0000000000 Binary files a/packaging/macos/krita-4.1_dmgBG.jpg and /dev/null differ diff --git a/packaging/macos/krita_dmgBG.jpg b/packaging/macos/krita_dmgBG.jpg new file mode 100644 index 0000000000..c425c42701 Binary files /dev/null and b/packaging/macos/krita_dmgBG.jpg differ diff --git a/packaging/macos/osxdeploy.sh b/packaging/macos/osxdeploy.sh index 4d36411969..0e1d1cfc00 100755 --- a/packaging/macos/osxdeploy.sh +++ b/packaging/macos/osxdeploy.sh @@ -1,562 +1,562 @@ #!/usr/bin/env bash # Krita tool to create dmg from installed source # Copies all files to a folder to be converted into the final dmg # osxdeploy.sh automates the creation of the release DMG. # default background and style are used if none provided # A short explanation of what it does: # - Copies krita.app contents to kritadmg folder # - Copies i/share to Contents/Resources excluding unnecesary files # - Copies translations, qml and quicklook PlugIns # - Copies i/plugins and i/lib/plugins to Contents/PlugIns # - Runs macdeployqt: macdeployqt is not built by default in ext_qt # build by: # cd ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src # make sub-macdeployqt-all # make sub-macdeployqt-install_subtargets # make install # the script changes dir to installation/bin to run macdeployqt as it can be buggy # if not runned from the same folder as the binary is on. # - Fix rpath from krita bin # - Find missing libraries from plugins and copy to Framworks or plugins. # This uses oTool iterative to find all unique libraries, then it searches each # library fond in folder, and if not found attempts to copy contents # to the appropiate folder, either Frameworks (if frameworks is in namefile, or # library has plugin isnot in path), or plugin if otherwise. # - Builds DMG # Building DMG creates a new dmg with the contents of # mounts the dmg and sets the style for dmg. # unmount # Compress resulting dmg into krita_nightly-.dmg # deletes temporary files. if test -z ${BUILDROOT}; then echo "ERROR: BUILDROOT env not set!" echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme" echo "exiting..." exit fi get_script_dir() { script_source="${BASH_SOURCE[0]}" # go to target until finding root. while [ -L "${script_source}" ]; do script_target="$(readlink ${script_source})" if [[ "${script_source}" = /* ]]; then script_source="$script_target" else script_dir="$(dirname "${script_source}")" script_source="${script_dir}/${script_target}" fi done echo "$(dirname ${script_source})" } DMG_title="krita" #if changed krita.temp.dmg must be deleted manually SCRIPT_SOURCE_DIR="$(get_script_dir)" # There is some duplication between build and deploy scripts # a config env file could would be a nice idea. KIS_SRC_DIR=${BUILDROOT}/krita KIS_INSTALL_DIR=${BUILDROOT}/i KIS_BUILD_DIR=${BUILDROOT}/kisbuild # only used for getting git sha number KRITA_DMG=${BUILDROOT}/kritadmg KRITA_DMG_TEMPLATE=${BUILDROOT}/kritadmg-template export PATH=${KIS_INSTALL_DIR}/bin:$PATH # flags for OSX environment # We only support from 10.11 up export MACOSX_DEPLOYMENT_TARGET=10.11 export QMAKE_MACOSX_DEPLOYMENT_TARGET=10.11 # Attempt to find python_version local_PY_MAYOR_VERSION=$(python -c "import sys; print(sys.version_info[0])") local_PY_MINOR_VERSION=$(python -c "import sys; print(sys.version_info[1])") PY_VERSION="${local_PY_MAYOR_VERSION}.${local_PY_MINOR_VERSION}" echo "Detected Python ${PY_VERSION}" print_usage () { echo "USAGE: osxdeploy.sh [-s=] [-style=] [-bg=]" echo "\t -s Code sign identity for codesign" echo "\t -style Style file defined from 'dmgstyle.sh' output" echo "\t -bg Set a background image for dmg folder" echo "\t osxdeploy needs an input image to add to the dmg background \t image recomended size is at least 950x500\n" } # Attempt to detach previous mouted DMG if [[ -d "/Volumes/${DMG_title}" ]]; then echo "WARNING: Another Krita DMG is mounted!" echo "Attempting eject…" hdiutil detach "/Volumes/${DMG_title}" if [ $? -ne 0 ]; then exit fi echo "Success!" fi # Parse input args for arg in "${@}"; do if [ "${arg}" = -bg=* -a -f "${arg#*=}" ]; then DMG_validBG=0 bg_filename=${arg#*=} echo "attempting to check background is valid jpg or png..." BG_FORMAT=$(sips --getProperty format ${bg_filename} | awk '{printf $2}') if [[ "png" = ${BG_FORMAT} || "jpeg" = ${BG_FORMAT} ]];then echo "valid image file" DMG_background=$(cd "$(dirname "${bg_filename}")"; pwd -P)/$(basename "${bg_filename}") DMG_validBG=1 # check imageDPI BG_DPI=$(sips --getProperty dpiWidth ${DMG_background} | grep dpi | awk '{print $2}') if [[ $(echo "${BG_DPI} > 150" | bc -l) -eq 1 ]]; then printf "WARNING: image dpi has an effect on apparent size! Check dpi is adequate for screen display if image appears very small Current dpi is: %s\n" ${BG_DPI} fi fi fi # If string starts with -sign if [[ ${arg} = -s=* ]]; then CODE_SIGNATURE="${arg#*=}" fi if [[ ${arg} = -style=* ]]; then style_filename="${arg#*=}" if [[ -f "${style_filename}" ]]; then DMG_STYLE="${style_filename}" fi fi if [[ ${arg} = "-h" || ${arg} = "--help" ]]; then print_usage exit fi done if [[ ! ${DMG_STYLE} ]]; then DMG_STYLE="${SCRIPT_SOURCE_DIR}/default.style" fi echo "Using style from: ${DMG_STYLE}" if [[ ${DMG_validBG} -eq 0 ]]; then echo "No jpg or png valid file detected!!" echo "Using default style" - DMG_background="${SCRIPT_SOURCE_DIR}/krita-4.1_dmgBG.jpg" + DMG_background="${SCRIPT_SOURCE_DIR}/krita_dmgBG.jpg" fi if [[ -z "${CODE_SIGNATURE}" ]]; then echo "WARNING: No signature provided, Code will not be signed" else printf 'Code will be signed with "%s"\n' "${CODE_SIGNATURE}" fi # Helper functions countArgs () { echo "${#}" } stringContains () { echo "$(grep "${2}" <<< "${1}")" } add_lib_to_list() { local llist=${2} if test -z "$(grep ${1##*/} <<< ${llist})" ; then local llist="${llist} ${1##*/} " fi echo "${llist}" } # Find all @rpath and Absolute to buildroot path libs # Add to libs_used # converts absolute buildroot path to @rpath find_needed_libs () { # echo "Analizing libraries with oTool..." >&2 local libs_used="" # input lib_lists founded for libFile in ${@}; do if test -z "$(file ${libFile} | grep 'Mach-O')" ; then # echo "skipping ${libFile}" >&2 continue fi oToolResult=$(otool -L ${libFile} | awk '{print $1}') resultArray=(${oToolResult}) # convert to array for lib in ${resultArray[@]:1}; do if test "${lib:0:1}" = "@"; then local libs_used=$(add_lib_to_list "${lib}" "${libs_used}") fi if [[ "${lib:0:${#BUILDROOT}}" = "${BUILDROOT}" ]]; then printf "Fixing %s: %s\n" "${libFile#${KRITA_DMG}/}" "${lib##*/}" >&2 if [[ "${lib##*/}" = "${libFile##*/}" ]]; then install_name_tool -id ${lib##*/} "${libFile}" else install_name_tool -change ${lib} "@rpath/${lib##*${BUILDROOT}/i/lib/}" "${libFile}" local libs_used=$(add_lib_to_list "${lib}" "${libs_used}") fi fi done done echo "${libs_used}" # return updated list } find_missing_libs (){ # echo "Searching for missing libs on deployment folders…" >&2 local libs_missing="" for lib in ${@}; do if test -z "$(find ${KRITA_DMG}/krita.app/Contents/ -name ${lib})"; then # echo "Adding ${lib} to missing libraries." >&2 libs_missing="${libs_missing} ${lib}" fi done echo "${libs_missing}" } copy_missing_libs () { for lib in ${@}; do result=$(find -L "${BUILDROOT}/i" -name "${lib}") if test $(countArgs ${result}) -eq 1; then if [ "$(stringContains "${result}" "plugin")" ]; then cp -pv ${result} ${KRITA_DMG}/krita.app/Contents/PlugIns/ krita_findmissinglibs "${KRITA_DMG}/krita.app/Contents/PlugIns/${result##*/}" else cp -pv ${result} ${KRITA_DMG}/krita.app/Contents/Frameworks/ krita_findmissinglibs "${KRITA_DMG}/krita.app/Contents/Frameworks/${result##*/}" fi else echo "${lib} might be a missing framework" if [ "$(stringContains "${result}" "framework")" ]; then echo "copying framework ${BUILDROOT}/i/lib/${lib}.framework to dmg" # rsync only included ${lib} Resources Versions rsync -priul ${BUILDROOT}/i/lib/${lib}.framework/${lib} ${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/ rsync -priul ${BUILDROOT}/i/lib/${lib}.framework/Resources ${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/ rsync -priul ${BUILDROOT}/i/lib/${lib}.framework/Versions ${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/ krita_findmissinglibs "$(find "${KRITA_DMG}/krita.app/Contents/Frameworks/${lib}.framework/" -perm u+x)" fi fi done } krita_findmissinglibs() { neededLibs=$(find_needed_libs "${@}") missingLibs=$(find_missing_libs ${neededLibs}) if test $(countArgs ${missingLibs}) -gt 0; then printf "Found missing libs: %s\n" "${missingLibs}" copy_missing_libs ${missingLibs} fi } strip_python_dmginstall() { # reduce size of framework python # Removes tests, installers, pyenv, distutils echo "Removing unnecesary files from Python.Framework to be packaged..." PythonFrameworkBase="${KRITA_DMG}/krita.app/Contents/Frameworks/Python.framework" cd ${PythonFrameworkBase} find . -name "test*" -type d | xargs rm -rf find "${PythonFrameworkBase}/Versions/${PY_VERSION}/bin" -not -name "python*" | xargs rm -f cd "${PythonFrameworkBase}/Versions/${PY_VERSION}/lib/python${PY_VERSION}" rm -rf distutils tkinter ensurepip venv lib2to3 idlelib } fix_python_framework() { # Fix python.framework rpath and slims down installation # fix library LD_RPATH excutable_path and loader_path. # It is intended to be used for Libraries inside Frameworks. fix_framework_library() { xargs -P4 -I FILE sh -c " install_name_tool -rpath ${KIS_INSTALL_DIR}/lib @loader_path/Frameworks \"${libFile}\" 2> /dev/null install_name_tool -add_rpath @loader_path/../../../ \"${libFile}\" 2> /dev/null " } # Start fixing all executables PythonFrameworkBase="${KRITA_DMG}/krita.app/Contents/Frameworks/Python.framework" install_name_tool -change @loader_path/../../../../libintl.9.dylib @loader_path/../../../libintl.9.dylib "${PythonFrameworkBase}/Python" install_name_tool -add_rpath @executable_path/../../../../../../../ "${PythonFrameworkBase}/Versions/Current/Resources/Python.app/Contents/MacOS/Python" install_name_tool -add_rpath @executable_path/../../../../ "${PythonFrameworkBase}/Versions/Current/bin/python${PY_VERSION}" install_name_tool -add_rpath @executable_path/../../../../ "${PythonFrameworkBase}/Versions/Current/bin/python${PY_VERSION}m" # Fix rpaths from Python.Framework # install_name_tool change @loader_path/../../../libz.1.dylib # Fix main library printf ${PythonFrameworkBase}/Python | fix_framework_library # find ${PythonFrameworkBase} -name "*.so" -not -type l | fix_framework_library } # Checks for macdeployqt # If not present attempts to install # If it fails shows an informatve message # (For now, macdeployqt is fundamental to deploy) macdeployqt_exists() { printf "Checking for macdeployqt... " if [[ ! -e "${KIS_INSTALL_DIR}/bin/macdeployqt" ]]; then printf "Not Found!\n" printf "Attempting to install macdeployqt\n" cd ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src make sub-macdeployqt-all make sub-macdeployqt-install_subtargets make install if [[ ! -e "${KIS_INSTALL_DIR}/bin/macdeployqt" ]]; then printf " ERROR: Failed to install macdeployqt! Compile and install from qt source directory Source code to build could be located in qttools/src in qt source dir: ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src From the source dir, build and install: make sub-macdeployqt-all make sub-macdeployqt-install_subtargets make install " printf "\nexiting...\n" exit else echo "Done!" fi else echo "Found!" fi } krita_deploy () { # check for macdeployqt macdeployqt_exists cd ${BUILDROOT} # Update files in krita.app echo "Deleting previous kritadmg run..." rm -rf ./krita.dmg ${KRITA_DMG} # Copy new builtFiles echo "Preparing ${KRITA_DMG} for deployment..." echo "Copying krita.app..." mkdir "${KRITA_DMG}" rsync -prul ${KIS_INSTALL_DIR}/bin/krita.app ${KRITA_DMG} mkdir -p ${KRITA_DMG}/krita.app/Contents/PlugIns mkdir -p ${KRITA_DMG}/krita.app/Contents/Frameworks echo "Copying share..." # Deletes old copies of translation and qml to be recreated cd ${KIS_INSTALL_DIR}/share/ rsync -prul --delete ./ \ --exclude krita_SRCS.icns \ --exclude aclocal \ --exclude doc \ --exclude ECM \ --exclude eigen3 \ --exclude emacs \ --exclude gettext \ --exclude gettext-0.19.8 \ --exclude info \ --exclude kf5 \ --exclude kservices5 \ --exclude man \ --exclude ocio \ --exclude pkgconfig \ --exclude mime \ --exclude translations \ --exclude qml \ ${KRITA_DMG}/krita.app/Contents/Resources cd ${BUILDROOT} echo "Copying translations..." rsync -prul ${KIS_INSTALL_DIR}/translations/ \ ${KRITA_DMG}/krita.app/Contents/Resources/translations echo "Copying kritaquicklook..." mkdir -p ${KRITA_DMG}/krita.app/Contents/Library/QuickLook rsync -prul ${KIS_INSTALL_DIR}/plugins/kritaquicklook.qlgenerator ${KRITA_DMG}/krita.app/Contents/Library/QuickLook cd ${KRITA_DMG}/krita.app/Contents ln -shF Resources share echo "Copying qml..." rsync -prul ${KIS_INSTALL_DIR}/qml Resources/qml echo "Copying plugins..." # exclude kritaquicklook.qlgenerator/ cd ${KIS_INSTALL_DIR}/plugins/ rsync -prul --delete --delete-excluded ./ \ --exclude kritaquicklook.qlgenerator \ ${KRITA_DMG}/krita.app/Contents/PlugIns cd ${BUILDROOT} rsync -prul ${KIS_INSTALL_DIR}/lib/kritaplugins/ ${KRITA_DMG}/krita.app/Contents/PlugIns # rsync -prul {KIS_INSTALL_DIR}/lib/libkrita* Frameworks/ # To avoid errors macdeployqt must be run from bin location # ext_qt will not build macdeployqt by default so it must be build manually # cd ${BUILDROOT}/depbuild/ext_qt/ext_qt-prefix/src/ext_qt/qttools/src # make sub-macdeployqt-all # make sub-macdeployqt-install_subtargets # make install echo "Running macdeployqt..." cd ${KIS_INSTALL_DIR}/bin ./macdeployqt ${KRITA_DMG}/krita.app \ -verbose=0 \ -executable=${KRITA_DMG}/krita.app/Contents/MacOS/krita \ -libpath=${KIS_INSTALL_DIR}/lib \ -qmldir=${KIS_INSTALL_DIR}/qml \ # -extra-plugins=${KIS_INSTALL_DIR}/lib/kritaplugins \ # -extra-plugins=${KIS_INSTALL_DIR}/lib/plugins \ # -extra-plugins=${KIS_INSTALL_DIR}/plugins cd ${BUILDROOT} echo "macdeployqt done!" echo "Copying python..." # Copy this framework last! # It is best that macdeployqt does not modify Python.framework # folders with period in name are treated as Frameworks for codesign rsync -prul ${KIS_INSTALL_DIR}/lib/Python.framework ${KRITA_DMG}/krita.app/Contents/Frameworks/ rsync -prul ${KIS_INSTALL_DIR}/lib/krita-python-libs ${KRITA_DMG}/krita.app/Contents/Frameworks/ # change perms on Python to allow header change chmod +w ${KRITA_DMG}/krita.app/Contents/Frameworks/Python.framework/Python fix_python_framework strip_python_dmginstall # fix python pyc # precompile all pyc so the dont alter signature echo "Precompiling all python files..." cd ${KRITA_DMG}/krita.app ${KIS_INSTALL_DIR}/bin/python -m compileall . &> /dev/null install_name_tool -delete_rpath @loader_path/../../../../lib ${KRITA_DMG}/krita.app/Contents/MacOS/krita rm -rf ${KRITA_DMG}/krita.app/Contents/PlugIns/kf5/org.kde.kwindowsystem.platforms # repair krita for plugins printf "Searching for missing libraries\n" krita_findmissinglibs $(find ${KRITA_DMG}/krita.app/Contents -type f -name "*.dylib" -or -name "*.so" -or -perm u+x) echo "Done!" } # helper to define function only once batch_codesign() { xargs -P4 -I FILE codesign -f -s "${CODE_SIGNATURE}" FILE } # Code sign must be done as recommended by apple "sign code inside out in individual stages" signBundle() { cd ${KRITA_DMG} # sign Frameworks and libs cd ${KRITA_DMG}/krita.app/Contents/Frameworks # remove debug version as both versions cant be signed. rm ${KRITA_DMG}/krita.app/Contents/Frameworks/QtScript.framework/Versions/Current/QtScript_debug find . -type f -name "*.dylib" -or -name "*.so" | batch_codesign find . -type d -name "*.framework" | xargs printf "%s/Versions/Current\n" | batch_codesign # Sign all other files in Framework (needed) # there are many files in python do we need to sign them all? find krita-python-libs -type f | batch_codesign # find python -type f | batch_codesign # Sing only libraries and plugins cd ${KRITA_DMG}/krita.app/Contents/PlugIns find . -type f | batch_codesign cd ${KRITA_DMG}/krita.app/Contents/Library/QuickLook printf "kritaquicklook.qlgenerator" | batch_codesign # It is recommended to sign every Resource file cd ${KRITA_DMG}/krita.app/Contents/Resources find . -type f | batch_codesign #Finally sign krita and krita.app printf "${KRITA_DMG}/krita.app/Contents/MacOS/krita" | batch_codesign printf "${KRITA_DMG}/krita.app" | batch_codesign } createDMG () { printf "Creating of dmg with contents of %s...\n" "${KRITA_DMG}" cd ${BUILDROOT} DMG_size=700 ## Build dmg from folder # create dmg on local system # usage of -fsargs minimze gaps at front of filesystem (reduce size) hdiutil create -srcfolder "${KRITA_DMG}" -volname "${DMG_title}" -fs HFS+ \ -fsargs "-c c=64,a=16,e=16" -format UDRW -size ${DMG_size}m krita.temp.dmg # Next line is only useful if we have a dmg as a template! # previous hdiutil must be uncommented # cp krita-template.dmg krita.dmg device=$(hdiutil attach -readwrite -noverify -noautoopen "krita.temp.dmg" | egrep '^/dev/' | sed 1q | awk '{print $1}') # rsync -priul --delete ${KRITA_DMG}/krita.app "/Volumes/${DMG_title}" # Set style for dmg if [[ ! -d "/Volumes/${DMG_title}/.background" ]]; then mkdir "/Volumes/${DMG_title}/.background" fi cp -v ${DMG_background} "/Volumes/${DMG_title}/.background/" mkdir "/Volumes/${DMG_title}/Terms of Use" cp -v "${KIS_SRC_DIR}/packaging/macos/Terms_of_use.rtf" "/Volumes/${DMG_title}/Terms of Use/" ln -s "/Applications" "/Volumes/${DMG_title}/Applications" ## Apple script to set style style="$(<"${DMG_STYLE}")" printf "${style}" "${DMG_title}" "${DMG_background##*/}" | osascript #Set Icon for DMG cp -v "${SCRIPT_SOURCE_DIR}/KritaIcon.icns" "/Volumes/${DMG_title}/.VolumeIcon.icns" SetFile -a C "/Volumes/${DMG_title}" chmod -Rf go-w "/Volumes/${DMG_title}" # ensure all writting operations to dmg are over sync hdiutil detach $device hdiutil convert "krita.temp.dmg" -format UDZO -imagekey -zlib-level=9 -o krita-out.dmg # Add git version number GIT_SHA=$(grep "#define KRITA_GIT_SHA1_STRING" ${KIS_BUILD_DIR}/libs/version/kritagitversion.h | awk '{gsub(/"/, "", $3); printf $3}') mv krita-out.dmg krita-nightly_${GIT_SHA}.dmg echo "moved krita-out.dmg to krita-nightly_${GIT_SHA}.dmg" rm krita.temp.dmg echo "dmg done!" } ####################### # Program starts!! ######################## # Run deploy command, instalation is assumed to exist in BUILDROOT/i krita_deploy # Code sign krita.app if signature given if [[ -n "${CODE_SIGNATURE}" ]]; then signBundle fi # Create DMG from files insiede ${KRITA_DMG} folder createDMG diff --git a/plugins/extensions/animationrenderer/AnimationRenderer.cpp b/plugins/extensions/animationrenderer/AnimationRenderer.cpp index 8bbb553aa6..718941a02e 100644 --- a/plugins/extensions/animationrenderer/AnimationRenderer.cpp +++ b/plugins/extensions/animationrenderer/AnimationRenderer.cpp @@ -1,206 +1,206 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "AnimationRenderer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include "DlgAnimationRenderer.h" #include #include "video_saver.h" #include "KisAnimationRenderingOptions.h" K_PLUGIN_FACTORY_WITH_JSON(AnimaterionRendererFactory, "kritaanimationrenderer.json", registerPlugin();) AnimaterionRenderer::AnimaterionRenderer(QObject *parent, const QVariantList &) : KisActionPlugin(parent) { // Shows the big dialog KisAction *action = createAction("render_animation"); action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION); connect(action, SIGNAL(triggered()), this, SLOT(slotRenderAnimation())); // Re-renders the image sequence as defined in the last render action = createAction("render_animation_again"); action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION); connect(action, SIGNAL(triggered()), this, SLOT(slotRenderSequenceAgain())); } AnimaterionRenderer::~AnimaterionRenderer() { } void AnimaterionRenderer::slotRenderAnimation() { KisImageWSP image = viewManager()->image(); if (!image) return; if (!image->animationInterface()->hasAnimation()) return; KisDocument *doc = viewManager()->document(); DlgAnimationRenderer dlgAnimationRenderer(doc, viewManager()->mainWindow()); dlgAnimationRenderer.setCaption(i18n("Render Animation")); if (dlgAnimationRenderer.exec() == QDialog::Accepted) { KisAnimationRenderingOptions encoderOptions = dlgAnimationRenderer.getEncoderOptions(); renderAnimationImpl(doc, encoderOptions); } } void AnimaterionRenderer::slotRenderSequenceAgain() { KisImageWSP image = viewManager()->image(); if (!image) return; if (!image->animationInterface()->hasAnimation()) return; KisDocument *doc = viewManager()->document(); KisConfig cfg(true); KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT"); KisAnimationRenderingOptions encoderOptions; encoderOptions.fromProperties(settings); renderAnimationImpl(doc, encoderOptions); } void AnimaterionRenderer::renderAnimationImpl(KisDocument *doc, KisAnimationRenderingOptions encoderOptions) { const QString frameMimeType = encoderOptions.frameMimeType; const QString framesDirectory = encoderOptions.resolveAbsoluteFramesDirectory(); const QString extension = KisMimeDatabase::suffixesForMimeType(frameMimeType).first(); const QString baseFileName = QString("%1/%2.%3").arg(framesDirectory) .arg(encoderOptions.basename) .arg(extension); /** * The dialog should ensure that the size of the video is even */ KIS_SAFE_ASSERT_RECOVER( !((encoderOptions.width & 0x1 || encoderOptions.height & 0x1) && (encoderOptions.videoMimeType == "video/mp4" || encoderOptions.videoMimeType == "video/x-matroska"))) { encoderOptions.width = encoderOptions.width + (encoderOptions.width & 0x1); encoderOptions.height = encoderOptions.height + (encoderOptions.height & 0x1); } const QSize scaledSize = doc->image()->bounds().size().scaled( encoderOptions.width, encoderOptions.height, Qt::KeepAspectRatio); if ((scaledSize.width() & 0x1 || scaledSize.height() & 0x1) && (encoderOptions.videoMimeType == "video/mp4" || encoderOptions.videoMimeType == "video/x-matroska")) { QString m = "Mastroska (.mkv)"; if (encoderOptions.videoMimeType == "video/mp4") { m = "Mpeg4 (.mp4)"; } qWarning() << m <<"requires width and height to be even, resize and try again!"; doc->setErrorMessage(i18n("%1 requires width and height to be even numbers. Please resize or crop the image before exporting.", m)); QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", doc->errorMessage())); return; } const bool batchMode = false; // TODO: fetch correctly! KisAsyncAnimationFramesSaveDialog exporter(doc->image(), KisTimeRange::fromTime(encoderOptions.firstFrame, encoderOptions.lastFrame), baseFileName, encoderOptions.sequenceStart, encoderOptions.frameExportConfig); exporter.setBatchMode(batchMode); KisAsyncAnimationFramesSaveDialog::Result result = exporter.regenerateRange(viewManager()->mainWindow()->viewManager()); // the folder could have been read-only or something else could happen if (encoderOptions.shouldEncodeVideo && result == KisAsyncAnimationFramesSaveDialog::RenderComplete) { const QString savedFilesMask = exporter.savedFilesMask(); const QString resultFile = encoderOptions.resolveAbsoluteVideoFilePath(); KIS_SAFE_ASSERT_RECOVER_NOOP(QFileInfo(resultFile).isAbsolute()) { const QFileInfo info(resultFile); QDir dir(info.absolutePath()); if (!dir.exists()) { dir.mkpath(info.absolutePath()); } KIS_SAFE_ASSERT_RECOVER_NOOP(dir.exists()); } - KisImportExportFilter::ConversionStatus res; + KisImportExportErrorCode res; QFile fi(resultFile); if (!fi.open(QIODevice::WriteOnly)) { qWarning() << "Could not open" << fi.fileName() << "for writing!"; - doc->setErrorMessage(i18n("Could not open %1 for writing!", fi.fileName())); - res = KisImportExportFilter::CreationError; + res = KisImportExportErrorCannotWrite(fi.error()); } else { fi.close(); } QScopedPointer encoder(new VideoSaver(doc, batchMode)); res = encoder->convert(doc, savedFilesMask, encoderOptions, batchMode); - if (res != KisImportExportFilter::OK) { - QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", doc->errorMessage())); + if (!res.isOk()) { + QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", res.errorMessage())); } if (encoderOptions.shouldDeleteSequence) { QDir d(framesDirectory); QStringList sequenceFiles = d.entryList(QStringList() << encoderOptions.basename + "*." + extension, QDir::Files); Q_FOREACH(const QString &f, sequenceFiles) { d.remove(f); } } } else if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) { viewManager()->mainWindow()->viewManager()->showFloatingMessage(i18n("Failed to render animation frames!"), QIcon()); } } #include "AnimationRenderer.moc" diff --git a/plugins/extensions/animationrenderer/video_saver.cpp b/plugins/extensions/animationrenderer/video_saver.cpp index 7c0d8f7840..2394d54e2b 100644 --- a/plugins/extensions/animationrenderer/video_saver.cpp +++ b/plugins/extensions/animationrenderer/video_saver.cpp @@ -1,342 +1,331 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "video_saver.h" #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "KisAnimationRenderingOptions.h" #include #include #include #include #include #include #include #include "KisPart.h" class KisFFMpegProgressWatcher : public QObject { Q_OBJECT public: KisFFMpegProgressWatcher(QFile &progressFile, int totalFrames) : m_progressFile(progressFile), m_totalFrames(totalFrames) { connect(&m_progressWatcher, SIGNAL(fileChanged(QString)), SLOT(slotFileChanged())); m_progressWatcher.addPath(m_progressFile.fileName()); } private Q_SLOTS: void slotFileChanged() { int currentFrame = -1; bool isEnded = false; while(!m_progressFile.atEnd()) { QString line = QString(m_progressFile.readLine()).remove(QChar('\n')); QStringList var = line.split("="); if (var[0] == "frame") { currentFrame = var[1].toInt(); } else if (var[0] == "progress") { isEnded = var[1] == "end"; } } if (isEnded) { emit sigProgressChanged(100); emit sigProcessingFinished(); } else { emit sigProgressChanged(100 * currentFrame / m_totalFrames); } } Q_SIGNALS: void sigProgressChanged(int percent); void sigProcessingFinished(); private: QFileSystemWatcher m_progressWatcher; QFile &m_progressFile; int m_totalFrames; }; class KisFFMpegRunner { public: KisFFMpegRunner(const QString &ffmpegPath) : m_cancelled(false), m_ffmpegPath(ffmpegPath) {} public: - KisImageBuilder_Result runFFMpeg(const QStringList &specialArgs, + KisImportExportErrorCode runFFMpeg(const QStringList &specialArgs, const QString &actionName, const QString &logPath, int totalFrames) { dbgFile << "runFFMpeg: specialArgs" << specialArgs << "actionName" << actionName << "logPath" << logPath << "totalFrames" << totalFrames; QTemporaryFile progressFile(QDir::tempPath() + QDir::separator() + "KritaFFmpegProgress.XXXXXX"); progressFile.open(); m_process.setStandardOutputFile(logPath); m_process.setProcessChannelMode(QProcess::MergedChannels); QStringList args; args << "-v" << "debug" << "-nostdin" << "-progress" << progressFile.fileName() << specialArgs; qDebug() << "\t" << m_ffmpegPath << args.join(" "); m_cancelled = false; m_process.start(m_ffmpegPath, args); return waitForFFMpegProcess(actionName, progressFile, m_process, totalFrames); } void cancel() { m_cancelled = true; m_process.kill(); } private: - KisImageBuilder_Result waitForFFMpegProcess(const QString &message, + KisImportExportErrorCode waitForFFMpegProcess(const QString &message, QFile &progressFile, QProcess &ffmpegProcess, int totalFrames) { KisFFMpegProgressWatcher watcher(progressFile, totalFrames); QProgressDialog progress(message, "", 0, 0, KisPart::instance()->currentMainwindow()); progress.setWindowModality(Qt::ApplicationModal); progress.setCancelButton(0); progress.setMinimumDuration(0); progress.setValue(0); progress.setRange(0, 100); QEventLoop loop; loop.connect(&watcher, SIGNAL(sigProcessingFinished()), SLOT(quit())); loop.connect(&ffmpegProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(quit())); loop.connect(&ffmpegProcess, SIGNAL(error(QProcess::ProcessError)), SLOT(quit())); loop.connect(&watcher, SIGNAL(sigProgressChanged(int)), &progress, SLOT(setValue(int))); if (ffmpegProcess.state() != QProcess::NotRunning) { loop.exec(); // wait for some erroneous case ffmpegProcess.waitForFinished(5000); } - KisImageBuilder_Result retval = KisImageBuilder_RESULT_OK; + KisImportExportErrorCode retval = ImportExportCodes::OK; if (ffmpegProcess.state() != QProcess::NotRunning) { // sorry... ffmpegProcess.kill(); - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; } else if (m_cancelled) { - retval = KisImageBuilder_RESULT_CANCEL; + retval = ImportExportCodes::Cancelled; } else if (ffmpegProcess.exitCode()) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; } return retval; } private: QProcess m_process; bool m_cancelled; QString m_ffmpegPath; }; VideoSaver::VideoSaver(KisDocument *doc, bool batchMode) : m_image(doc->image()) , m_doc(doc) , m_batchMode(batchMode) { } VideoSaver::~VideoSaver() { } KisImageSP VideoSaver::image() { return m_image; } -KisImageBuilder_Result VideoSaver::encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options) +KisImportExportErrorCode VideoSaver::encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options) { if (!QFileInfo(options.ffmpegPath).exists()) { m_doc->setErrorMessage(i18n("ffmpeg could not be found at %1", options.ffmpegPath)); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::Failure; } - KisImageBuilder_Result result = KisImageBuilder_RESULT_OK; + KisImportExportErrorCode resultOuter = ImportExportCodes::OK; KisImageAnimationInterface *animation = m_image->animationInterface(); const int sequenceNumberingOffset = options.sequenceStart; const KisTimeRange clipRange(sequenceNumberingOffset + options.firstFrame, sequenceNumberingOffset + options.lastFrame); // export dimensions could be off a little bit, so the last force option tweaks the pixels for the export to work const QString exportDimensions = QString("scale=w=") .append(QString::number(options.width)) .append(":h=") .append(QString::number(options.height)) .append(":force_original_aspect_ratio=decrease"); const QString resultFile = options.resolveAbsoluteVideoFilePath(); const QDir videoDir(QFileInfo(resultFile).absolutePath()); const QFileInfo info(resultFile); const QString suffix = info.suffix().toLower(); const QString palettePath = videoDir.filePath("palette.png"); const QStringList additionalOptionsList = options.customFFMpegOptions.split(' ', QString::SkipEmptyParts); QScopedPointer runner(new KisFFMpegRunner(options.ffmpegPath)); if (suffix == "gif") { { QStringList args; args << "-r" << QString::number(options.frameRate) << "-start_number" << QString::number(clipRange.start()) << "-i" << savedFilesMask << "-vf" << "palettegen" << "-y" << palettePath; - KisImageBuilder_Result result = + KisImportExportErrorCode result = runner->runFFMpeg(args, i18n("Fetching palette..."), videoDir.filePath("log_generate_palette_gif.log"), clipRange.duration()); - if (result != KisImageBuilder_RESULT_OK) { + if (!result.isOk()) { return result; } } { QStringList args; args << "-r" << QString::number(options.frameRate) << "-start_number" << QString::number(clipRange.start()) << "-i" << savedFilesMask << "-i" << palettePath << "-lavfi" << "[0:v][1:v] paletteuse" << "-y" << resultFile; // if we are exporting out at a different image size, we apply scaling filter if (m_image->width() != options.width || m_image->height() != options.height) { args << "-vf" << exportDimensions; } dbgFile << "savedFilesMask" << savedFilesMask << "start" << QString::number(clipRange.start()) << "duration" << clipRange.duration(); - KisImageBuilder_Result result = + KisImportExportErrorCode result = runner->runFFMpeg(args, i18n("Encoding frames..."), videoDir.filePath("log_encode_gif.log"), clipRange.duration()); - if (result != KisImageBuilder_RESULT_OK) { + if (!result.isOk()) { return result; } } } else { QStringList args; args << "-r" << QString::number(options.frameRate) << "-start_number" << QString::number(clipRange.start()) << "-i" << savedFilesMask; QFileInfo audioFileInfo = animation->audioChannelFileName(); if (options.includeAudio && audioFileInfo.exists()) { const int msecStart = clipRange.start() * 1000 / animation->framerate(); const int msecDuration = clipRange.duration() * 1000 / animation->framerate(); const QTime startTime = QTime::fromMSecsSinceStartOfDay(msecStart); const QTime durationTime = QTime::fromMSecsSinceStartOfDay(msecDuration); const QString ffmpegTimeFormat("H:m:s.zzz"); args << "-ss" << startTime.toString(ffmpegTimeFormat); args << "-t" << durationTime.toString(ffmpegTimeFormat); args << "-i" << audioFileInfo.absoluteFilePath(); } // if we are exporting out at a different image size, we apply scaling filter // export options HAVE to go after input options, so make sure this is after the audio import if (m_image->width() != options.width || m_image->height() != options.height) { args << "-vf" << exportDimensions; } args << additionalOptionsList << "-y" << resultFile; - result = runner->runFFMpeg(args, i18n("Encoding frames..."), + resultOuter = runner->runFFMpeg(args, i18n("Encoding frames..."), videoDir.filePath("log_encode.log"), clipRange.duration()); } - return result; + return resultOuter; } -KisImportExportFilter::ConversionStatus VideoSaver::convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode) +KisImportExportErrorCode VideoSaver::convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode) { VideoSaver videoSaver(document, batchMode); - KisImageBuilder_Result res = videoSaver.encode(savedFilesMask, options); - - if (res == KisImageBuilder_RESULT_OK) { - return KisImportExportFilter::OK; - - } else if (res == KisImageBuilder_RESULT_CANCEL) { - return KisImportExportFilter::ProgressCancelled; - - }else { - document->setErrorMessage(i18n("FFMpeg failed to convert the image sequence. Check the logfile in your output directory for more information.")); - } - - return KisImportExportFilter::InternalError; + KisImportExportErrorCode res = videoSaver.encode(savedFilesMask, options); + return res; } #include "video_saver.moc" diff --git a/plugins/extensions/animationrenderer/video_saver.h b/plugins/extensions/animationrenderer/video_saver.h index 9f2fd15751..e9cb05e217 100644 --- a/plugins/extensions/animationrenderer/video_saver.h +++ b/plugins/extensions/animationrenderer/video_saver.h @@ -1,73 +1,73 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef VIDEO_SAVER_H_ #define VIDEO_SAVER_H_ #include #include "kis_types.h" #include "KisImageBuilderResult.h" #include class KisFFMpegRunner; /* The KisImageBuilder_Result definitions come from kis_png_converter.h here */ class KisDocument; class KisAnimationRenderingOptions; class VideoSaver : public QObject { Q_OBJECT public: /** * @brief VideoSaver * This is the object that takes an animation document and config and tells ffmpeg * to render it. Log files are generated here too. * @param doc the document to use for rendering. * @param ffmpegPath the path to the ffmpeg executable. * @param batchMode whether Krita is in batchmde and we can thus not show gui widgets. */ VideoSaver(KisDocument* doc, bool batchMode); ~VideoSaver() override; /** * @brief image * @return get the image used by this exporter. */ KisImageSP image(); /** * @brief encode the main encoding function. * This in turn calls runFFMpeg, which is a private function inside this class. * @param filename the filename to which to render the animation. * @param configuration the configuration * @return whether it is successful or had another failure. */ - KisImageBuilder_Result encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options); + KisImportExportErrorCode encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options); - static KisImportExportFilter::ConversionStatus convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode); + static KisImportExportErrorCode convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode); private: KisImageSP m_image; KisDocument* m_doc; bool m_batchMode; }; #endif diff --git a/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp b/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp index 1e2049502b..b99a1b89cf 100644 --- a/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp +++ b/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp @@ -1,144 +1,144 @@ /* * This file is part of Krita * * Copyright (c) 2006 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_wdg_fastcolortransfer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgfastcolortransfer.h" KisWdgFastColorTransfer::KisWdgFastColorTransfer(QWidget * parent) : KisConfigWidget(parent) { m_widget = new Ui_WdgFastColorTransfer(); m_widget->setupUi(this); m_widget->fileNameURLRequester->setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); connect(m_widget->fileNameURLRequester, SIGNAL(textChanged(QString)), this, SIGNAL(sigConfigurationItemChanged())); } KisWdgFastColorTransfer::~KisWdgFastColorTransfer() { delete m_widget; } void KisWdgFastColorTransfer::setConfiguration(const KisPropertiesConfigurationSP config) { QVariant value; if (config->getProperty("filename", value)) { widget()->fileNameURLRequester->setFileName(value.toString()); } } KisPropertiesConfigurationSP KisWdgFastColorTransfer::configuration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("colortransfer", 1); QString fileName = this->widget()->fileNameURLRequester->fileName(); if (fileName.isEmpty()) return config; KisPaintDeviceSP ref; dbgPlugins << "Use as reference file : " << fileName; KisDocument *d = KisPart::instance()->createDocument(); KisImportExportManager manager(d); - KisImportExportFilter::ConversionStatus status = manager.importDocument(fileName, QString()); - dbgPlugins << "import returned status" << status; + KisImportExportErrorCode status = manager.importDocument(fileName, QString()); + dbgPlugins << "import returned status" << status.errorMessage(); KisImageWSP importedImage = d->image(); if (importedImage) { ref = importedImage->projection(); } if (!ref) { dbgPlugins << "No reference image was specified."; delete d; return config; } // Convert ref to LAB const KoColorSpace* labCS = KoColorSpaceRegistry::instance()->lab16(); if (!labCS) { dbgPlugins << "The LAB colorspace is not available."; delete d; return config; } dbgPlugins << "convert ref to lab"; ref->convertTo(labCS, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); // Compute the means and sigmas of ref double meanL_ref = 0., meanA_ref = 0., meanB_ref = 0.; double sigmaL_ref = 0., sigmaA_ref = 0., sigmaB_ref = 0.; KisSequentialConstIterator refIt(ref, importedImage->bounds()); while (refIt.nextPixel()) { const quint16* data = reinterpret_cast(refIt.oldRawData()); quint32 L = data[0]; quint32 A = data[1]; quint32 B = data[2]; meanL_ref += L; meanA_ref += A; meanB_ref += B; sigmaL_ref += L * L; sigmaA_ref += A * A; sigmaB_ref += B * B; } double totalSize = 1. / (importedImage->width() * importedImage->height()); meanL_ref *= totalSize; meanA_ref *= totalSize; meanB_ref *= totalSize; sigmaL_ref *= totalSize; sigmaA_ref *= totalSize; sigmaB_ref *= totalSize; dbgPlugins << totalSize << "" << meanL_ref << "" << meanA_ref << "" << meanB_ref << "" << sigmaL_ref << "" << sigmaA_ref << "" << sigmaB_ref; config->setProperty("filename", fileName); config->setProperty("meanL", meanL_ref); config->setProperty("meanA", meanA_ref); config->setProperty("meanB", meanB_ref); config->setProperty("sigmaL", sigmaL_ref); config->setProperty("sigmaA", sigmaA_ref); config->setProperty("sigmaB", sigmaB_ref); delete d; return config; } diff --git a/plugins/impex/brush/CMakeLists.txt b/plugins/impex/brush/CMakeLists.txt index ffce44e4d0..3f44e9e589 100644 --- a/plugins/impex/brush/CMakeLists.txt +++ b/plugins/impex/brush/CMakeLists.txt @@ -1,26 +1,28 @@ +add_subdirectory(tests) + set(kritabrushexport_PART_SRCS kis_brush_export.cpp KisAnimatedBrushAnnotation.cpp ) ki18n_wrap_ui(kritabrushexport_PART_SRCS wdg_export_gih.ui) add_library(kritabrushexport MODULE ${kritabrushexport_PART_SRCS}) target_link_libraries(kritabrushexport kritalibbrush kritalibpaintop kritaui kritaimpex) install(TARGETS kritabrushexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritabrushimport_PART_SRCS kis_brush_import.cpp KisAnimatedBrushAnnotation.cpp ) ki18n_wrap_ui(kritabrushimport_PART_SRCS ) add_library(kritabrushimport MODULE ${kritabrushimport_PART_SRCS}) target_link_libraries(kritabrushimport kritalibbrush kritaui) install(TARGETS kritabrushimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_brush.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/brush/kis_brush_export.cpp b/plugins/impex/brush/kis_brush_export.cpp index 74705909d2..755b2015b9 100644 --- a/plugins/impex/brush/kis_brush_export.cpp +++ b/plugins/impex/brush/kis_brush_export.cpp @@ -1,228 +1,228 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_brush_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct KisBrushExportOptions { qreal spacing; bool mask; int brushStyle; int selectionMode; QString name; }; K_PLUGIN_FACTORY_WITH_JSON(KisBrushExportFactory, "krita_brush_export.json", registerPlugin();) KisBrushExport::KisBrushExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisBrushExport::~KisBrushExport() { } -KisImportExportFilter::ConversionStatus KisBrushExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisBrushExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { // XXX: Loading the parasite itself was commented out -- needs investigation // KisAnnotationSP annotation = document->savingImage()->annotation("ImagePipe Parasite"); // KisPipeBrushParasite parasite; // if (annotation) { // QBuffer buf(const_cast(&annotation->annotation())); // buf.open(QBuffer::ReadOnly); // parasite.loadFromDevice(&buf); // buf.close(); // } KisBrushExportOptions exportOptions; if (document->savingImage()->dynamicPropertyNames().contains("brushspacing")) { exportOptions.spacing = document->savingImage()->property("brushspacing").toFloat(); } else { exportOptions.spacing = configuration->getInt("spacing"); } if (!configuration->getString("name").isEmpty()) { exportOptions.name = configuration->getString("name"); } else { exportOptions.name = document->savingImage()->objectName(); } exportOptions.mask = configuration->getBool("mask"); exportOptions.selectionMode = configuration->getInt("selectionMode"); exportOptions.brushStyle = configuration->getInt("brushStyle"); KisGbrBrush *brush = 0; if (mimeType() == "image/x-gimp-brush") { brush = new KisGbrBrush(filename()); } else if (mimeType() == "image/x-gimp-brush-animated") { brush = new KisImagePipeBrush(filename()); } else { - return KisImportExportFilter::BadMimeType; + return ImportExportCodes::FileFormatIncorrect; } qApp->processEvents(); // For vector layers to be updated QRect rc = document->savingImage()->bounds(); brush->setName(exportOptions.name); brush->setSpacing(exportOptions.spacing); brush->setUseColorAsMask(exportOptions.mask); KisImagePipeBrush *pipeBrush = dynamic_cast(brush); if (pipeBrush) { // Create parasite. XXX: share with KisCustomBrushWidget QVector< QVector > devices; devices.push_back(QVector()); KoProperties properties; properties.setProperty("visible", true); QList layers = document->savingImage()->root()->childNodes(QStringList("KisLayer"), properties); Q_FOREACH (KisNodeSP node, layers) { devices[0].push_back(node->projection().data()); } QVector modes; switch (exportOptions.selectionMode) { case 0: modes.push_back(KisParasite::Constant); break; case 1: modes.push_back(KisParasite::Random); break; case 2: modes.push_back(KisParasite::Incremental); break; case 3: modes.push_back(KisParasite::Pressure); break; case 4: modes.push_back(KisParasite::Angular); break; default: modes.push_back(KisParasite::Incremental); } KisPipeBrushParasite parasite; // XXX: share code with KisImagePipeBrush, when we figure out how to support more gih features parasite.dim = devices.count(); // XXX Change for multidim! : parasite.ncells = devices.at(0).count(); parasite.rank[0] = parasite.ncells; // ### This can mask some bugs, be careful here in the future parasite.selection[0] = modes.at(0); // XXX needs movement! parasite.setBrushesCount(); pipeBrush->setParasite(parasite); pipeBrush->setDevices(devices, rc.width(), rc.height()); } else { QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image.save("~/bla.png"); brush->setImage(image); brush->setBrushTipImage(image); } brush->setWidth(rc.width()); brush->setHeight(rc.height()); if (brush->saveToDevice(io)) { - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } else { - return KisImportExportFilter::CreationError; + return ImportExportCodes::Failure; } } KisPropertiesConfigurationSP KisBrushExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("spacing", 1.0); cfg->setProperty("name", ""); cfg->setProperty("mask", true); cfg->setProperty("selectionMode", 0); cfg->setProperty("brushStyle", 0); return cfg; } KisConfigWidget *KisBrushExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &to) const { KisWdgOptionsBrush *wdg = new KisWdgOptionsBrush(parent); if (to == "image/x-gimp-brush") { wdg->groupBox->setVisible(false); } else if (to == "image/x-gimp-brush-animated") { wdg->groupBox->setVisible(true); } return wdg; } void KisBrushExport::initializeCapabilities() { QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "Gimp Brushes"); if (mimeType() == "image/x-gimp-brush-animated") { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); } } void KisWdgOptionsBrush::setConfiguration(const KisPropertiesConfigurationSP cfg) { spacingWidget->setSpacing(false, cfg->getDouble("spacing")); nameLineEdit->setText(cfg->getString("name")); colorAsMask->setChecked(cfg->getBool("mask")); brushStyle->setCurrentIndex(cfg->getInt("brushStyle")); cmbSelectionMode->setCurrentIndex(cfg->getInt("selectionMode")); } KisPropertiesConfigurationSP KisWdgOptionsBrush::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("spacing", spacingWidget->spacing()); cfg->setProperty("name", nameLineEdit->text()); cfg->setProperty("mask", colorAsMask->isChecked()); cfg->setProperty("selectionMode", cmbSelectionMode->currentIndex()); cfg->setProperty("brushStyle", brushStyle->currentIndex()); return cfg; } #include "kis_brush_export.moc" diff --git a/plugins/impex/brush/kis_brush_export.h b/plugins/impex/brush/kis_brush_export.h index 57c5f1449f..681c343ef7 100644 --- a/plugins/impex/brush/kis_brush_export.h +++ b/plugins/impex/brush/kis_brush_export.h @@ -1,68 +1,68 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_Brush_EXPORT_H_ #define _KIS_Brush_EXPORT_H_ #include #include #include #include #include class KisWdgOptionsBrush : public KisConfigWidget, public Ui::WdgExportGih { Q_OBJECT public: KisWdgOptionsBrush(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); connect(this->brushStyle, SIGNAL(currentIndexChanged(int)), SLOT(enableSelectionMedthod(int))); } void setConfiguration(const KisPropertiesConfigurationSP cfg) override; KisPropertiesConfigurationSP configuration() const override; public Q_SLOTS: void enableSelectionMedthod(int value) { if (value == 0) { cmbSelectionMode->setEnabled(false); } else { cmbSelectionMode->setEnabled(true); } } }; class KisBrushExport : public KisImportExportFilter { Q_OBJECT public: KisBrushExport(QObject *parent, const QVariantList &); ~KisBrushExport() override; - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/brush/kis_brush_import.cpp b/plugins/impex/brush/kis_brush_import.cpp index fb681bf25e..56bc545c11 100644 --- a/plugins/impex/brush/kis_brush_import.cpp +++ b/plugins/impex/brush/kis_brush_import.cpp @@ -1,123 +1,123 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_brush_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisBrushImportFactory, "krita_brush_import.json", registerPlugin();) KisBrushImport::KisBrushImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisBrushImport::~KisBrushImport() { } -KisImportExportFilter::ConversionStatus KisBrushImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisBrushImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisBrush *brush; if (mimeType() == "image/x-gimp-brush") { brush = new KisGbrBrush(filename()); } else if (mimeType() == "image/x-gimp-brush-animated") { brush = new KisImagePipeBrush(filename()); } else { - return KisImportExportFilter::BadMimeType; + return ImportExportCodes::FileFormatIncorrect; } if (!brush->loadFromDevice(io)) { delete brush; - return KisImportExportFilter::InvalidFormat; + return ImportExportCodes::FileFormatIncorrect; } if (!brush->valid()) { delete brush; - return KisImportExportFilter::InvalidFormat; + return ImportExportCodes::FileFormatIncorrect;; } const KoColorSpace *colorSpace = 0; if (brush->hasColor()) { colorSpace = KoColorSpaceRegistry::instance()->rgb8(); } else { colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), ""); } KisImageSP image = new KisImage(document->createUndoStore(), brush->width(), brush->height(), colorSpace, brush->name()); image->setProperty("brushspacing", brush->spacing()); KisImagePipeBrush *pipeBrush = dynamic_cast(brush); if (pipeBrush) { QVector brushes = pipeBrush->brushes(); for(int i = brushes.size(); i > 0; i--) { KisGbrBrush *subbrush = brushes.at(i - 1); const KoColorSpace *subColorSpace = 0; if (brush->hasColor()) { subColorSpace = KoColorSpaceRegistry::instance()->rgb8(); } else { subColorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), ""); } KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255, subColorSpace); layer->paintDevice()->convertFromQImage(subbrush->brushTipImage(), 0, 0, 0); image->addNode(layer, image->rootLayer()); } KisAnnotationSP ann = new KisAnimatedBrushAnnotation(pipeBrush->parasite()); image->addAnnotation(ann); } else { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255, colorSpace); layer->paintDevice()->convertFromQImage(brush->brushTipImage(), 0, 0, 0); image->addNode(layer, image->rootLayer(), 0); } document->setCurrentImage(image); delete brush; - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_brush_import.moc" diff --git a/plugins/impex/brush/kis_brush_import.h b/plugins/impex/brush/kis_brush_import.h index ee4f870acd..86b91a20a6 100644 --- a/plugins/impex/brush/kis_brush_import.h +++ b/plugins/impex/brush/kis_brush_import.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_Brush_IMPORT_H_ #define _KIS_Brush_IMPORT_H_ #include #include class KisBrushImport : public KisImportExportFilter { Q_OBJECT public: KisBrushImport(QObject *parent, const QVariantList &); ~KisBrushImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/brush/tests/CMakeLists.txt b/plugins/impex/brush/tests/CMakeLists.txt new file mode 100644 index 0000000000..d150e0f759 --- /dev/null +++ b/plugins/impex/brush/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisBrushTest.cpp + TEST_NAME KisBrushTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/brush/tests/KisBrushTest.cpp similarity index 63% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/brush/tests/KisBrushTest.cpp index 6ca66688f8..32877619f1 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/brush/tests/KisBrushTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "kis_svg_test.h" +#include "KisBrushTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString BrushMimetype = "image/x-gimp-brush"; + + + +void KisBrushTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), BrushMimetype); +} + + +void KisBrushTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), BrushMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisBrushTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), BrushMimetype); +} + + + +KISTEST_MAIN(KisBrushTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/brush/tests/KisBrushTest.h similarity index 73% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/brush/tests/KisBrushTest.h index 5c19b0cda6..0defe86df7 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/brush/tests/KisBrushTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _KIS_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_BRUSH_TEST_H_ +#define _KIS_BRUSH_TEST_H_ #include -class KisJpegTest : public QObject +class KisBrushTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_BRUSH_TEST_H_ + diff --git a/plugins/impex/brush/tests/data/incorrectFormatFile.txt b/plugins/impex/brush/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/brush/tests/data/readonlyFile.txt b/plugins/impex/brush/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/brush/tests/data/writeonlyFile.txt b/plugins/impex/brush/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/csv/csv_loader.cpp b/plugins/impex/csv/csv_loader.cpp index 6feedd40bc..7ebf6d03fb 100644 --- a/plugins/impex/csv/csv_loader.cpp +++ b/plugins/impex/csv/csv_loader.cpp @@ -1,489 +1,488 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "csv_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "csv_read_line.h" #include "csv_layer_record.h" CSVLoader::CSVLoader(KisDocument *doc, bool batchMode) : m_image(0) , m_doc(doc) , m_batchMode(batchMode) , m_stop(false) { } CSVLoader::~CSVLoader() { } -KisImageBuilder_Result CSVLoader::decode(QIODevice *io, const QString &filename) +KisImportExportErrorCode CSVLoader::decode(QIODevice *io, const QString &filename) { QString field; int idx; int frame = 0; QString projName; int width = 0; int height = 0; int frameCount = 1; float framerate = 24.0; float pixelRatio = 1.0; int projNameIdx = -1; int widthIdx = -1; int heightIdx = -1; int frameCountIdx = -1; int framerateIdx = -1; int pixelRatioIdx = -1; QVector layers; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); idx = filename.lastIndexOf(QRegExp("[\\/]")); QString base = (idx == -1) ? QString() : filename.left(idx + 1); //include separator QString path = filename; if (path.right(4).toUpper() == ".CSV") path = path.left(path.size() - 4); //according to the QT docs, the slash is a universal directory separator path.append(".frames/"); - KisImageBuilder_Result retval = KisImageBuilder_RESULT_OK; + KisImportExportErrorCode retval = ImportExportCodes::OK; dbgFile << "pos:" << io->pos(); CSVReadLine readLine; QScopedPointer importDoc(KisPart::instance()->createDocument()); importDoc->setInfiniteAutoSaveInterval(); importDoc->setFileBatchMode(true); KisView *setView(0); if (!m_batchMode) { // TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater() // //show the statusbar message even if no view // Q_FOREACH (KisView* view, KisPart::instance()->views()) { // if (view && view->document() == m_doc) { // setView = view; // break; // } // } // if (!setView) { // QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar(); // if (sb) { // sb->showMessage(i18n("Loading CSV file...")); // } // } else { // emit m_doc->statusBarMessage(i18n("Loading CSV file...")); // } // emit m_doc->sigProgress(0); // connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); } int step = 0; do { qApp->processEvents(); if (m_stop) { - retval = KisImageBuilder_RESULT_CANCEL; + retval = ImportExportCodes::Cancelled; break; } if ((idx = readLine.nextLine(io)) <= 0) { if ((idx < 0) ||(step < 5)) - retval = KisImageBuilder_RESULT_FAILURE; - + retval = ImportExportCodes::FileFormatIncorrect; break; } field = readLine.nextField(); //first field of the line if (field.isNull()) continue; //empty row switch (step) { case 0 : //skip first row step = 1; break; case 1 : //scene header names step = 2; for (idx = 0; !field.isNull(); idx++) { if (field == "Project Name") { projNameIdx = idx; } else if (field == "Width") { widthIdx = idx; } else if (field == "Height") { heightIdx = idx; } else if (field == "Frame Count") { frameCountIdx = idx; } else if (field == "Frame Rate") { framerateIdx = idx; } else if (field == "Pixel Aspect Ratio") { pixelRatioIdx = idx; } field= readLine.nextField(); } break; case 2 : //scene header values step= 3; for (idx= 0; !field.isNull(); idx++) { if (idx == projNameIdx) { projName = field; } else if (idx == widthIdx) { width = field.toInt(); } else if (idx == heightIdx) { height = field.toInt(); } else if (idx == frameCountIdx) { frameCount = field.toInt(); if (frameCount < 1) frameCount= 1; } else if (idx == framerateIdx) { framerate = field.toFloat(); } else if (idx == pixelRatioIdx) { pixelRatio = field.toFloat(); } field= readLine.nextField(); } if ((width < 1) || (height < 1)) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } retval = createNewImage(width, height, pixelRatio, projName.isNull() ? filename : projName); break; case 3 : //create level headers if (field[0] != '#') break; for (; !(field = readLine.nextField()).isNull(); ) { CSVLayerRecord* layerRecord = new CSVLayerRecord(); layers.append(layerRecord); } readLine.rewind(); field = readLine.nextField(); step = 4; Q_FALLTHROUGH(); case 4 : //level header if (field == "#Layers") { //layer name for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->name = field; break; } if (field == "#Density") { //layer opacity for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->density = field.toFloat(); break; } if (field == "#Blending") { //layer blending mode for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->blending = field; break; } if (field == "#Visible") { //layer visibility for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->visible = field.toInt(); break; } if (field == "#Folder") { //CSV 1.1 folder location for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->path = validPath(field, base); break; } if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break; step = 5; Q_FALLTHROUGH(); case 5 : //frames if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break; for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) { CSVLayerRecord* layer = layers.at(idx); if (layer->last != field) { if (!m_batchMode) { //emit m_doc->sigProgress((frame * layers.size() + idx) * 100 / // (frameCount * layers.size())); } retval = setLayer(layer, importDoc.data(), path); layer->last = field; layer->frame = frame; } } frame++; break; } - } while (retval == KisImageBuilder_RESULT_OK); + } while (retval.isOk()); //finish the layers - if (retval == KisImageBuilder_RESULT_OK) { + if (retval.isOk()) { if (m_image) { KisImageAnimationInterface *animation = m_image->animationInterface(); if (frame > frameCount) frameCount = frame; animation->setFullClipRange(KisTimeRange::fromTime(0,frameCount - 1)); animation->setFramerate((int)framerate); } for (idx = 0; idx < layers.size(); idx++) { CSVLayerRecord* layer = layers.at(idx); //empty layers without any pictures are dropped if ((layer->frame > 0) || !layer->last.isEmpty()) { retval = setLayer(layer, importDoc.data(), path); - if (retval != KisImageBuilder_RESULT_OK) + if (!retval.isOk()) break; } } } if (m_image) { //insert the existing layers by the right order for (idx = layers.size() - 1; idx >= 0; idx--) { CSVLayerRecord* layer = layers.at(idx); if (layer->layer) { m_image->addNode(layer->layer, m_image->root()); } } m_image->unlock(); } qDeleteAll(layers); io->close(); if (!m_batchMode) { // disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); // emit m_doc->sigProgress(100); if (!setView) { QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar(); if (sb) { sb->clearMessage(); } } else { emit m_doc->clearStatusBarMessage(); } } QApplication::restoreOverrideCursor(); return retval; } QString CSVLoader::convertBlending(const QString &blending) { if (blending == "Color") return COMPOSITE_OVER; if (blending == "Behind") return COMPOSITE_BEHIND; if (blending == "Erase") return COMPOSITE_ERASE; // "Shade" if (blending == "Light") return COMPOSITE_LINEAR_LIGHT; if (blending == "Colorize") return COMPOSITE_COLORIZE; if (blending == "Hue") return COMPOSITE_HUE; if (blending == "Add") return COMPOSITE_ADD; if (blending == "Sub") return COMPOSITE_INVERSE_SUBTRACT; if (blending == "Multiply") return COMPOSITE_MULT; if (blending == "Screen") return COMPOSITE_SCREEN; // "Replace" // "Substitute" if (blending == "Difference") return COMPOSITE_DIFF; if (blending == "Divide") return COMPOSITE_DIVIDE; if (blending == "Overlay") return COMPOSITE_OVERLAY; if (blending == "Light2") return COMPOSITE_DODGE; if (blending == "Shade2") return COMPOSITE_BURN; if (blending == "HardLight") return COMPOSITE_HARD_LIGHT; if (blending == "SoftLight") return COMPOSITE_SOFT_LIGHT_PHOTOSHOP; if (blending == "GrainExtract") return COMPOSITE_GRAIN_EXTRACT; if (blending == "GrainMerge") return COMPOSITE_GRAIN_MERGE; if (blending == "Sub2") return COMPOSITE_SUBTRACT; if (blending == "Darken") return COMPOSITE_DARKEN; if (blending == "Lighten") return COMPOSITE_LIGHTEN; if (blending == "Saturation") return COMPOSITE_SATURATION; return COMPOSITE_OVER; } QString CSVLoader::validPath(const QString &path,const QString &base) { //replace Windows directory separators with the universal / QString tryPath= QString(path).replace(QString("\\"), QString("/")); int i = tryPath.lastIndexOf("/"); if (i == (tryPath.size() - 1)) tryPath= tryPath.left(i); //remove the ending separator if exists if (QFileInfo(tryPath).isDir()) return tryPath.append("/"); QString scan(tryPath); i = -1; while ((i= (scan.lastIndexOf("/",i) - 1)) > 0) { //avoid testing if the next level will be the default xxxx.layers folder if ((i >= 6) && (scan.mid(i - 6, 7) == ".layers")) continue; tryPath= QString(base).append(scan.mid(i + 2)); //base already ending with a / if (QFileInfo(tryPath).isDir()) return tryPath.append("/"); } return QString(); //NULL string } -KisImageBuilder_Result CSVLoader::setLayer(CSVLayerRecord* layer, KisDocument *importDoc, const QString &path) +KisImportExportErrorCode CSVLoader::setLayer(CSVLayerRecord* layer, KisDocument *importDoc, const QString &path) { bool result = true; if (layer->channel == 0) { //create a new document layer float opacity = layer->density; if (opacity > 1.0) opacity = 1.0; else if (opacity < 0.0) opacity = 0.0; const KoColorSpace* cs = m_image->colorSpace(); const QString layerName = (layer->name).isEmpty() ? m_image->nextLayerName() : layer->name; KisPaintLayer* paintLayer = new KisPaintLayer(m_image, layerName, (quint8)(opacity * OPACITY_OPAQUE_U8), cs); paintLayer->setCompositeOpId(convertBlending(layer->blending)); paintLayer->setVisible(layer->visible); paintLayer->enableAnimation(); layer->layer = paintLayer; layer->channel = qobject_cast (paintLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); } if (!layer->last.isEmpty()) { //png image QString filename = layer->path.isNull() ? path : layer->path; filename.append(layer->last); result = importDoc->openUrl(QUrl::fromLocalFile(filename), KisDocument::DontAddToRecent); if (result) layer->channel->importFrame(layer->frame, importDoc->image()->projection(), 0); } else { //blank layer->channel->addKeyframe(layer->frame); } - return (result) ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE; + return (result) ? ImportExportCodes::OK : ImportExportCodes::Failure; } -KisImageBuilder_Result CSVLoader::createNewImage(int width, int height, float ratio, const QString &name) +KisImportExportErrorCode CSVLoader::createNewImage(int width, int height, float ratio, const QString &name) { //the CSV is RGBA 8bits, sRGB if (!m_image) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), 0); if (cs) m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, name); - if (!m_image) return KisImageBuilder_RESULT_FAILURE; + if (!m_image) return ImportExportCodes::Failure; m_image->setResolution(ratio, 1.0); m_image->lock(); } - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -KisImageBuilder_Result CSVLoader::buildAnimation(QIODevice *io, const QString &filename) +KisImportExportErrorCode CSVLoader::buildAnimation(QIODevice *io, const QString &filename) { return decode(io, filename); } KisImageSP CSVLoader::image() { return m_image; } void CSVLoader::cancel() { m_stop = true; } diff --git a/plugins/impex/csv/csv_loader.h b/plugins/impex/csv/csv_loader.h index 1b419aa031..8a1d1b5ef4 100644 --- a/plugins/impex/csv/csv_loader.h +++ b/plugins/impex/csv/csv_loader.h @@ -1,62 +1,62 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CSV_LOADER_H_ #define CSV_LOADER_H_ #include #include #include "kis_image.h" #include "kritaui_export.h" -#include +#include class KisDocument; #include "csv_layer_record.h" class CSVLoader : public QObject { Q_OBJECT public: CSVLoader(KisDocument* doc, bool batchMode); ~CSVLoader() override; - KisImageBuilder_Result buildAnimation(QIODevice *io, const QString &filename); + KisImportExportErrorCode buildAnimation(QIODevice *io, const QString &filename); KisImageSP image(); private: - KisImageBuilder_Result decode(QIODevice *io, const QString &filename); - KisImageBuilder_Result setLayer(CSVLayerRecord* , KisDocument* ,const QString &); - KisImageBuilder_Result createNewImage(int, int, float, const QString &); + KisImportExportErrorCode decode(QIODevice *io, const QString &filename); + KisImportExportErrorCode setLayer(CSVLayerRecord* , KisDocument* ,const QString &); + KisImportExportErrorCode createNewImage(int, int, float, const QString &); QString convertBlending(const QString &); QString validPath(const QString &, const QString &); private Q_SLOTS: void cancel(); private: KisImageSP m_image; KisDocument* m_doc; bool m_batchMode; bool m_stop; }; #endif diff --git a/plugins/impex/csv/csv_saver.cpp b/plugins/impex/csv/csv_saver.cpp index e76ee539b8..ea6e7a9ba1 100644 --- a/plugins/impex/csv/csv_saver.cpp +++ b/plugins/impex/csv/csv_saver.cpp @@ -1,478 +1,476 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "csv_saver.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "csv_layer_record.h" CSVSaver::CSVSaver(KisDocument *doc, bool batchMode) : m_image(doc->savingImage()) , m_doc(doc) , m_batchMode(batchMode) , m_stop(false) { } CSVSaver::~CSVSaver() { } KisImageSP CSVSaver::image() { return m_image; } -KisImageBuilder_Result CSVSaver::encode(QIODevice *io) +KisImportExportErrorCode CSVSaver::encode(QIODevice *io) { int idx; int start, end; KisNodeSP node; QByteArray ba; KisKeyframeSP keyframe; QVector layers; KisImageAnimationInterface *animation = m_image->animationInterface(); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // XXX: Stream was unused? // //DataStream instead of TextStream for correct line endings // QDataStream stream(&f); //Using the original local path QString path = m_doc->localFilePath(); if (path.right(4).toUpper() == ".CSV") path = path.left(path.size() - 4); else { // something is wrong: the local file name is not .csv! // trying the given (probably temporary) filename as well KIS_SAFE_ASSERT_RECOVER(0 && "Wrong extension of the saved file!") { path = path.left(path.size() - 4); } } path.append(".frames"); //create directory QDir dir(path); if (!dir.exists()) { dir.mkpath("."); } //according to the QT docs, the slash is a universal directory separator path.append("/"); node = m_image->rootLayer()->firstChild(); //TODO: correct handling of the layer tree. //for now, only top level paint layers are saved idx = 0; while (node) { if (node->inherits("KisLayer")) { KisLayer* paintLayer = qobject_cast(node.data()); CSVLayerRecord* layerRecord = new CSVLayerRecord(); layers.prepend(layerRecord); //reverse order! layerRecord->name = paintLayer->name(); layerRecord->name.replace(QRegExp("[\"\\r\\n]"), "_"); if (layerRecord->name.isEmpty()) layerRecord->name= QString("Unnamed-%1").arg(idx); layerRecord->visible = (paintLayer->visible()) ? 1 : 0; layerRecord->density = (float)(paintLayer->opacity()) / OPACITY_OPAQUE_U8; layerRecord->blending = convertToBlending(paintLayer->compositeOpId()); layerRecord->layer = paintLayer; layerRecord->channel = paintLayer->original()->keyframeChannel(); layerRecord->last = ""; layerRecord->frame = 0; idx++; } node = node->nextSibling(); } KisTimeRange range = animation->fullClipRange(); start = (range.isValid()) ? range.start() : 0; if (!range.isInfinite()) { end = range.end(); if (end < start) end = start; } else { //undefined length, searching for the last keyframe end = start; for (idx = 0; idx < layers.size(); idx++) { KisRasterKeyframeChannel *channel = layers.at(idx)->channel; if (channel) { keyframe = channel->lastKeyframe(); if ( (!keyframe.isNull()) && (keyframe->time() > end) ) end = keyframe->time(); } } } //create temporary doc for exporting QScopedPointer exportDoc(KisPart::instance()->createDocument()); createTempImage(exportDoc.data()); - KisImageBuilder_Result retval= KisImageBuilder_RESULT_OK; + KisImportExportErrorCode retval= ImportExportCodes::OK; if (!m_batchMode) { // TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater() //emit m_doc->statusBarMessage(i18n("Saving CSV file...")); //emit m_doc->sigProgress(0); //connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); } int frame = start; int step = 0; do { qApp->processEvents(); if (m_stop) { - retval = KisImageBuilder_RESULT_CANCEL; + retval = ImportExportCodes::Cancelled; break; } switch(step) { case 0 : //first row if (io->write("UTF-8, TVPaint, \"CSV 1.0\"\r\n") < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; } break; case 1 : //scene header names if (io->write("Project Name, Width, Height, Frame Count, Layer Count, Frame Rate, Pixel Aspect Ratio, Field Mode\r\n") < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; } break; case 2 : //scene header values ba = QString("\"%1\", ").arg(m_image->objectName()).toUtf8(); if (io->write(ba.data()) < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } ba = QString("%1, %2, ").arg(m_image->width()).arg(m_image->height()).toUtf8(); if (io->write(ba.data()) < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } ba = QString("%1, %2, ").arg(end - start + 1).arg(layers.size()).toUtf8(); if (io->write(ba.data()) < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } //the framerate is an integer here ba = QString("%1, ").arg((double)(animation->framerate()),0,'f',6).toUtf8(); if (io->write(ba.data()) < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } ba = QString("%1, Progressive\r\n").arg((double)(m_image->xRes() / m_image->yRes()),0,'f',6).toUtf8(); if (io->write(ba.data()) < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } break; case 3 : //layer header values if (io->write("#Layers") < 0) { //Layers - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", \"%1\"").arg(layers.at(idx)->name).toUtf8(); if (io->write(ba.data()) < 0) break; } break; case 4 : if (io->write("\r\n#Density") < 0) { //Density - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", %1").arg((double)(layers.at(idx)->density), 0, 'f', 6).toUtf8(); if (io->write(ba.data()) < 0) break; } break; case 5 : if (io->write("\r\n#Blending") < 0) { //Blending - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", \"%1\"").arg(layers.at(idx)->blending).toUtf8(); if (io->write(ba.data()) < 0) break; } break; case 6 : if (io->write("\r\n#Visible") < 0) { //Visible - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", %1").arg(layers.at(idx)->visible).toUtf8(); if (io->write(ba.data()) < 0) break; } if (idx < layers.size()) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; } break; default : //frames if (frame > end) { if (io->write("\r\n") < 0) - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; step = 8; break; } ba = QString("\r\n#%1").arg(frame, 5, 10, QChar('0')).toUtf8(); if (io->write(ba.data()) < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } for (idx = 0; idx < layers.size(); idx++) { CSVLayerRecord *layer = layers.at(idx); KisRasterKeyframeChannel *channel = layer->channel; if (channel) { if (frame == start) { keyframe = channel->activeKeyframeAt(frame); } else { keyframe = channel->keyframeAt(frame); } } else { keyframe.clear(); // without animation } if ( !keyframe.isNull() || (frame == start) ) { if (!m_batchMode) { //emit m_doc->sigProgress(((frame - start) * layers.size() + idx) * 100 / // ((end - start) * layers.size())); } retval = getLayer(layer, exportDoc.data(), keyframe, path, frame, idx); - if (retval != KisImageBuilder_RESULT_OK) + if (!retval.isOk()) break; } ba = QString(", \"%1\"").arg(layer->last).toUtf8(); if (io->write(ba.data()) < 0) break; } if (idx < layers.size()) - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; frame++; step = 6; //keep step here break; } step++; - } while((retval == KisImageBuilder_RESULT_OK) && (step < 8)); + } while((retval.isOk()) && (step < 8)); qDeleteAll(layers); // io->close(); it seems this is not required anymore if (!m_batchMode) { //disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); //emit m_doc->sigProgress(100); //emit m_doc->clearStatusBarMessage(); } QApplication::restoreOverrideCursor(); return retval; } QString CSVSaver::convertToBlending(const QString &opid) { if (opid == COMPOSITE_OVER) return "Color"; if (opid == COMPOSITE_BEHIND) return "Behind"; if (opid == COMPOSITE_ERASE) return "Erase"; // "Shade" if (opid == COMPOSITE_LINEAR_LIGHT) return "Light"; if (opid == COMPOSITE_COLORIZE) return "Colorize"; if (opid == COMPOSITE_HUE) return "Hue"; if ((opid == COMPOSITE_ADD) || (opid == COMPOSITE_LINEAR_DODGE)) return "Add"; if (opid == COMPOSITE_INVERSE_SUBTRACT) return "Sub"; if (opid == COMPOSITE_MULT) return "Multiply"; if (opid == COMPOSITE_SCREEN) return "Screen"; // "Replace" // "Substitute" if (opid == COMPOSITE_DIFF) return "Difference"; if (opid == COMPOSITE_DIVIDE) return "Divide"; if (opid == COMPOSITE_OVERLAY) return "Overlay"; if (opid == COMPOSITE_DODGE) return "Light2"; if (opid == COMPOSITE_BURN) return "Shade2"; if (opid == COMPOSITE_HARD_LIGHT) return "HardLight"; if ((opid == COMPOSITE_SOFT_LIGHT_PHOTOSHOP) || (opid == COMPOSITE_SOFT_LIGHT_SVG)) return "SoftLight"; if (opid == COMPOSITE_GRAIN_EXTRACT) return "GrainExtract"; if (opid == COMPOSITE_GRAIN_MERGE) return "GrainMerge"; if (opid == COMPOSITE_SUBTRACT) return "Sub2"; if (opid == COMPOSITE_DARKEN) return "Darken"; if (opid == COMPOSITE_LIGHTEN) return "Lighten"; if (opid == COMPOSITE_SATURATION) return "Saturation"; return "Color"; } -KisImageBuilder_Result CSVSaver::getLayer(CSVLayerRecord* layer, KisDocument* exportDoc, KisKeyframeSP keyframe, const QString &path, int frame, int idx) +KisImportExportErrorCode CSVSaver::getLayer(CSVLayerRecord* layer, KisDocument* exportDoc, KisKeyframeSP keyframe, const QString &path, int frame, int idx) { //render to the temp layer KisImageSP image = exportDoc->savingImage(); if (!image) image= exportDoc->image(); KisPaintDeviceSP device = image->rootLayer()->firstChild()->projection(); if (!keyframe.isNull()) { layer->channel->fetchFrame(keyframe, device); } else { device->makeCloneFrom(layer->layer->projection(),image->bounds()); // without animation } QRect bounds = device->exactBounds(); if (bounds.isEmpty()) { layer->last = ""; //empty frame - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } layer->last = QString("frame%1-%2.png").arg(idx + 1,5,10,QChar('0')).arg(frame,5,10,QChar('0')); QString filename = path; filename.append(layer->last); //save to PNG KisSequentialConstIterator it(device, image->bounds()); const KoColorSpace* cs = device->colorSpace(); bool isThereAlpha = false; while (it.nextPixel()) { if (cs->opacityU8(it.oldRawData()) != OPACITY_OPAQUE_U8) { isThereAlpha = true; break; } } if (!KisPNGConverter::isColorSpaceSupported(cs)) { device = new KisPaintDevice(*device.data()); device->convertTo(KoColorSpaceRegistry::instance()->rgb8()); } KisPNGOptions options; options.alpha = isThereAlpha; options.interlace = false; options.compression = 8; options.tryToSaveAsIndexed = false; options.transparencyFillColor = QColor(0,0,0); options.saveSRGBProfile = true; //TVPaint can use only sRGB options.forceSRGB = false; KisPNGConverter kpc(exportDoc); - KisImageBuilder_Result result = kpc.buildFile(filename, image->bounds(), + KisImportExportErrorCode result = kpc.buildFile(filename, image->bounds(), image->xRes(), image->yRes(), device, image->beginAnnotations(), image->endAnnotations(), options, (KisMetaData::Store* )0 ); return result; } void CSVSaver::createTempImage(KisDocument* exportDoc) { exportDoc->setInfiniteAutoSaveInterval(); exportDoc->setFileBatchMode(true); KisImageSP exportImage = new KisImage(exportDoc->createUndoStore(), m_image->width(), m_image->height(), m_image->colorSpace(), QString()); exportImage->setResolution(m_image->xRes(), m_image->yRes()); exportDoc->setCurrentImage(exportImage); KisPaintLayer* paintLayer = new KisPaintLayer(exportImage, "paint device", OPACITY_OPAQUE_U8); exportImage->addNode(paintLayer, exportImage->rootLayer(), KisLayerSP(0)); } -KisImageBuilder_Result CSVSaver::buildAnimation(QIODevice *io) +KisImportExportErrorCode CSVSaver::buildAnimation(QIODevice *io) { - if (!m_image) { - return KisImageBuilder_RESULT_EMPTY; - } + KIS_ASSERT_RECOVER_RETURN_VALUE(m_image, ImportExportCodes::InternalError); return encode(io); } void CSVSaver::cancel() { m_stop = true; } diff --git a/plugins/impex/csv/csv_saver.h b/plugins/impex/csv/csv_saver.h index 395f84ef23..d382d8a818 100644 --- a/plugins/impex/csv/csv_saver.h +++ b/plugins/impex/csv/csv_saver.h @@ -1,61 +1,61 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CSV_SAVER_H_ #define CSV_SAVER_H_ #include #include #include "kis_types.h" #include "kis_raster_keyframe_channel.h" #include "kis_png_converter.h" -/* The KisImageBuilder_Result definitions come from kis_png_converter.h here */ +/* The KisImportExportErrorCode definitions come from kis_png_converter.h here */ #include "csv_layer_record.h" class KisDocument; class CSVSaver : public QObject { Q_OBJECT public: CSVSaver(KisDocument* doc, bool batchMode); ~CSVSaver() override; - KisImageBuilder_Result buildAnimation(QIODevice *io); + KisImportExportErrorCode buildAnimation(QIODevice *io); KisImageSP image(); private: - KisImageBuilder_Result encode(QIODevice *io); - KisImageBuilder_Result getLayer(CSVLayerRecord* , KisDocument* , KisKeyframeSP, const QString &, int, int); + KisImportExportErrorCode encode(QIODevice *io); + KisImportExportErrorCode getLayer(CSVLayerRecord* , KisDocument* , KisKeyframeSP, const QString &, int, int); void createTempImage(KisDocument* ); QString convertToBlending(const QString &); private Q_SLOTS: void cancel(); private: KisImageSP m_image; KisDocument* m_doc; bool m_batchMode; bool m_stop; }; #endif diff --git a/plugins/impex/csv/kis_csv_export.cpp b/plugins/impex/csv/kis_csv_export.cpp index 402dc70614..f16924aeab 100644 --- a/plugins/impex/csv/kis_csv_export.cpp +++ b/plugins/impex/csv/kis_csv_export.cpp @@ -1,80 +1,70 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_csv_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "csv_saver.h" K_PLUGIN_FACTORY_WITH_JSON(KisCSVExportFactory, "krita_csv_export.json", registerPlugin();) KisCSVExport::KisCSVExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisCSVExport::~KisCSVExport() { } -KisImportExportFilter::ConversionStatus KisCSVExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisCSVExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { CSVSaver kpc(document, batchMode()); - KisImageBuilder_Result res; - - if ((res = kpc.buildAnimation(io)) == KisImageBuilder_RESULT_OK) { - dbgFile <<"success!"; - return KisImportExportFilter::OK; - } - dbgFile <<" Result =" << res; - - if (res == KisImageBuilder_RESULT_CANCEL) - return KisImportExportFilter::ProgressCancelled; - - return KisImportExportFilter::InternalError; + KisImportExportErrorCode res = kpc.buildAnimation(io); + return res; } void KisCSVExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("AnimationCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "CSV"); addCapability(KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + RGBAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); } #include "kis_csv_export.moc" diff --git a/plugins/impex/csv/kis_csv_export.h b/plugins/impex/csv/kis_csv_export.h index f2063884fb..2ceaa38c4c 100644 --- a/plugins/impex/csv/kis_csv_export.h +++ b/plugins/impex/csv/kis_csv_export.h @@ -1,38 +1,38 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_CSV_EXPORT_H_ #define _KIS_CSV_EXPORT_H_ #include #include class KisCSVExport : public KisImportExportFilter { Q_OBJECT public: KisCSVExport(QObject *parent, const QVariantList &); ~KisCSVExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/csv/kis_csv_import.cpp b/plugins/impex/csv/kis_csv_import.cpp index 4f39d5eb0d..fb9a555d7c 100644 --- a/plugins/impex/csv/kis_csv_import.cpp +++ b/plugins/impex/csv/kis_csv_import.cpp @@ -1,77 +1,55 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_csv_import.h" #include #include #include #include #include #include #include "csv_loader.h" K_PLUGIN_FACTORY_WITH_JSON(CSVImportFactory, "krita_csv_import.json", registerPlugin();) KisCSVImport::KisCSVImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisCSVImport::~KisCSVImport() { } -KisImportExportFilter::ConversionStatus KisCSVImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisCSVImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { CSVLoader ib(document, batchMode()); - - KisImageBuilder_Result result = ib.buildAnimation(io, filename()); - - switch (result) { - case KisImageBuilder_RESULT_UNSUPPORTED: - return KisImportExportFilter::NotImplemented; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_EXIST: - case KisImageBuilder_RESULT_NOT_LOCAL: - qDebug() << "ib returned KisImageBuilder_RESULT_NOT_LOCAL"; - return KisImportExportFilter::FileNotFound; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InternalError; - case KisImageBuilder_RESULT_CANCEL: - return KisImportExportFilter::ProgressCancelled; - case KisImageBuilder_RESULT_OK: - document -> setCurrentImage( ib.image()); - return KisImportExportFilter::OK; - default: - return KisImportExportFilter::StorageCreationError; + KisImportExportErrorCode result = ib.buildAnimation(io, filename()); + if (result.isOk()) { + document->setCurrentImage(ib.image()); } - return KisImportExportFilter::InternalError; + return result; } #include diff --git a/plugins/impex/csv/kis_csv_import.h b/plugins/impex/csv/kis_csv_import.h index dd342f05e4..edf85ef992 100644 --- a/plugins/impex/csv/kis_csv_import.h +++ b/plugins/impex/csv/kis_csv_import.h @@ -1,36 +1,36 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_CSV_IMPORT_H_ #define _KIS_CSV_IMPORT_H_ #include #include class KisCSVImport : public KisImportExportFilter { Q_OBJECT public: KisCSVImport(QObject *parent, const QVariantList &); ~KisCSVImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/csv/tests/data/incorrectFormatFile.txt b/plugins/impex/csv/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/csv/tests/data/readonlyFile.txt b/plugins/impex/csv/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/csv/tests/data/writeonlyFile.txt b/plugins/impex/csv/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/csv/tests/kis_csv_test.cpp b/plugins/impex/csv/tests/kis_csv_test.cpp index f68aeff2dd..bd804f3aed 100644 --- a/plugins/impex/csv/tests/kis_csv_test.cpp +++ b/plugins/impex/csv/tests/kis_csv_test.cpp @@ -1,37 +1,58 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_csv_test.h" #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString CsvMimetype = "text/csv"; + void KisCsvTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList()); } + + +void KisCsvTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), CsvMimetype); +} + + +void KisCsvTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), CsvMimetype); +} + + +void KisCsvTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), CsvMimetype); +} + QTEST_MAIN(KisCsvTest) diff --git a/plugins/impex/csv/tests/kis_csv_test.h b/plugins/impex/csv/tests/kis_csv_test.h index c1df27049b..ae4fb3c76f 100644 --- a/plugins/impex/csv/tests/kis_csv_test.h +++ b/plugins/impex/csv/tests/kis_csv_test.h @@ -1,31 +1,34 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_CSV_TEST_H_ #define _KIS_CSV_TEST_H_ #include class KisCsvTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; #endif diff --git a/plugins/impex/exr/exr_converter.cc b/plugins/impex/exr/exr_converter.cc index 7bcdd2cca7..dbd24add84 100644 --- a/plugins/impex/exr/exr_converter.cc +++ b/plugins/impex/exr/exr_converter.cc @@ -1,1373 +1,1399 @@ /* * Copyright (c) 2005 Adrian Page * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "exr_converter.h" #include #include #include #include #include #include #include "exr_extra_tags.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include #include #include #include #include #include #include "kis_kra_savexml_visitor.h" +#include + // Do not translate! #define HDR_LAYER "HDR Layer" template struct Rgba { _T_ r; _T_ g; _T_ b; _T_ a; }; struct ExrGroupLayerInfo; struct ExrLayerInfoBase { ExrLayerInfoBase() : colorSpace(0), parent(0) { } const KoColorSpace* colorSpace; QString name; const ExrGroupLayerInfo* parent; }; struct ExrGroupLayerInfo : public ExrLayerInfoBase { ExrGroupLayerInfo() : groupLayer(0) {} KisGroupLayerSP groupLayer; }; enum ImageType { IT_UNKNOWN, IT_FLOAT16, IT_FLOAT32, IT_UNSUPPORTED }; struct ExrPaintLayerInfo : public ExrLayerInfoBase { ExrPaintLayerInfo() : imageType(IT_UNKNOWN) { } ImageType imageType; QMap< QString, QString> channelMap; ///< first is either R, G, B or A second is the EXR channel name struct Remap { Remap(const QString& _original, const QString& _current) : original(_original), current(_current) { } QString original; QString current; }; QList< Remap > remappedChannels; ///< this is used to store in the metadata the mapping between exr channel name, and channels used in Krita void updateImageType(ImageType channelType); }; void ExrPaintLayerInfo::updateImageType(ImageType channelType) { if (imageType == IT_UNKNOWN) { imageType = channelType; } else if (imageType != channelType) { imageType = IT_UNSUPPORTED; } } struct ExrPaintLayerSaveInfo { QString name; ///< name of the layer with a "." at the end (ie "group1.group2.layer1.") KisPaintDeviceSP layerDevice; KisPaintLayerSP layer; QList channels; Imf::PixelType pixelType; }; struct EXRConverter::Private { Private() : doc(0) , alphaWasModified(false) , showNotifications(false) {} KisImageSP image; KisDocument *doc; bool alphaWasModified; bool showNotifications; QString errorMessage; template void unmultiplyAlpha(typename WrapperType::pixel_type *pixel); template void decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype); template void decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype); QDomDocument loadExtraLayersInfo(const Imf::Header &header); bool checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set exrLayerNames); void makeLayerNamesUnique(QList& informationObjects); void recBuildPaintLayerSaveInfo(QList& informationObjects, const QString& name, KisGroupLayerSP parent); void reportLayersNotSaved(const QSet &layersNotSaved); QString fetchExtraLayersInfo(QList& informationObjects); }; EXRConverter::EXRConverter(KisDocument *doc, bool showNotifications) : d(new Private) { d->doc = doc; d->showNotifications = showNotifications; // Set thread count for IlmImf library Imf::setGlobalThreadCount(QThread::idealThreadCount()); dbgFile << "EXR Threadcount was set to: " << QThread::idealThreadCount(); } EXRConverter::~EXRConverter() { } ImageType imfTypeToKisType(Imf::PixelType type) { switch (type) { case Imf::UINT: case Imf::NUM_PIXELTYPES: return IT_UNSUPPORTED; case Imf::HALF: return IT_FLOAT16; case Imf::FLOAT: return IT_FLOAT32; default: qFatal("Out of bound enum"); return IT_UNKNOWN; } } const KoColorSpace* kisTypeToColorSpace(QString model, ImageType imageType) { const QString profileName = KisConfig(false).readEntry("ExrDefaultColorProfile", KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(model)); switch (imageType) { case IT_FLOAT16: return KoColorSpaceRegistry::instance()->colorSpace(model, Float16BitsColorDepthID.id(), profileName); case IT_FLOAT32: return KoColorSpaceRegistry::instance()->colorSpace(model, Float32BitsColorDepthID.id(), profileName); case IT_UNKNOWN: case IT_UNSUPPORTED: return 0; default: qFatal("Out of bound enum"); return 0; } } template static inline T alphaEpsilon() { return static_cast(HALF_EPSILON); } template static inline T alphaNoiseThreshold() { return static_cast(0.01); // 1% } static inline bool qFuzzyCompare(half p1, half p2) { return std::abs(p1 - p2) < float(HALF_EPSILON); } static inline bool qFuzzyIsNull(half h) { return std::abs(h) < float(HALF_EPSILON); } template struct RgbPixelWrapper { typedef T channel_type; typedef Rgba pixel_type; RgbPixelWrapper(Rgba &_pixel) : pixel(_pixel) {} inline T alpha() const { return pixel.a; } inline bool checkMultipliedColorsConsistent() const { return !(std::abs(pixel.a) < alphaEpsilon() && (!qFuzzyIsNull(pixel.r) || !qFuzzyIsNull(pixel.g) || !qFuzzyIsNull(pixel.b))); } inline bool checkUnmultipliedColorsConsistent(const Rgba &mult) const { const T alpha = std::abs(pixel.a); return alpha >= alphaNoiseThreshold() || (qFuzzyCompare(T(pixel.r * alpha), mult.r) && qFuzzyCompare(T(pixel.g * alpha), mult.g) && qFuzzyCompare(T(pixel.b * alpha), mult.b)); } inline void setUnmultiplied(const Rgba &mult, T newAlpha) { const T absoluteAlpha = std::abs(newAlpha); pixel.r = mult.r / absoluteAlpha; pixel.g = mult.g / absoluteAlpha; pixel.b = mult.b / absoluteAlpha; pixel.a = newAlpha; } Rgba &pixel; }; template struct GrayPixelWrapper { typedef T channel_type; typedef typename KoGrayTraits::Pixel pixel_type; GrayPixelWrapper(pixel_type &_pixel) : pixel(_pixel) {} inline T alpha() const { return pixel.alpha; } inline bool checkMultipliedColorsConsistent() const { return !(std::abs(pixel.alpha) < alphaEpsilon() && !qFuzzyIsNull(pixel.gray)); } inline bool checkUnmultipliedColorsConsistent(const pixel_type &mult) const { const T alpha = std::abs(pixel.alpha); return alpha >= alphaNoiseThreshold() || qFuzzyCompare(T(pixel.gray * alpha), mult.gray); } inline void setUnmultiplied(const pixel_type &mult, T newAlpha) { const T absoluteAlpha = std::abs(newAlpha); pixel.gray = mult.gray / absoluteAlpha; pixel.alpha = newAlpha; } pixel_type &pixel; }; template void EXRConverter::Private::unmultiplyAlpha(typename WrapperType::pixel_type *pixel) { typedef typename WrapperType::pixel_type pixel_type; typedef typename WrapperType::channel_type channel_type; WrapperType srcPixel(*pixel); if (!srcPixel.checkMultipliedColorsConsistent()) { channel_type newAlpha = srcPixel.alpha(); pixel_type __dstPixelData; WrapperType dstPixel(__dstPixelData); /** * Division by a tiny alpha may result in an overflow of half * value. That is why we use safe iterational approach. */ while (1) { dstPixel.setUnmultiplied(srcPixel.pixel, newAlpha); if (dstPixel.checkUnmultipliedColorsConsistent(srcPixel.pixel)) { break; } newAlpha += alphaEpsilon(); alphaWasModified = true; } *pixel = dstPixel.pixel; } else if (srcPixel.alpha() > 0.0) { srcPixel.setUnmultiplied(srcPixel.pixel, srcPixel.alpha()); } } template void multiplyAlpha(Pixel *pixel) { if (alphaPos >= 0) { T alpha = pixel->data[alphaPos]; if (alpha > 0.0) { for (int i = 0; i < size; ++i) { if (i != alphaPos) { pixel->data[i] *= alpha; } } pixel->data[alphaPos] = alpha; } } } template void EXRConverter::Private::decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype) { typedef Rgba<_T_> Rgba; QVector pixels(width * height); bool hasAlpha = info.channelMap.contains("A"); Imf::FrameBuffer frameBuffer; Rgba* frameBufferData = (pixels.data()) - xstart - ystart * width; frameBuffer.insert(info.channelMap["R"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->r, sizeof(Rgba) * 1, sizeof(Rgba) * width)); frameBuffer.insert(info.channelMap["G"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->g, sizeof(Rgba) * 1, sizeof(Rgba) * width)); frameBuffer.insert(info.channelMap["B"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->b, sizeof(Rgba) * 1, sizeof(Rgba) * width)); if (hasAlpha) { frameBuffer.insert(info.channelMap["A"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->a, sizeof(Rgba) * 1, sizeof(Rgba) * width)); } file.setFrameBuffer(frameBuffer); file.readPixels(ystart, height + ystart - 1); Rgba *rgba = pixels.data(); QRect paintRegion(xstart, ystart, width, height); KisSequentialIterator it(layer->paintDevice(), paintRegion); while (it.nextPixel()) { if (hasAlpha) { unmultiplyAlpha >(rgba); } typename KoRgbTraits<_T_>::Pixel* dst = reinterpret_cast::Pixel*>(it.rawData()); dst->red = rgba->r; dst->green = rgba->g; dst->blue = rgba->b; if (hasAlpha) { dst->alpha = rgba->a; } else { dst->alpha = 1.0; } ++rgba; } } template void EXRConverter::Private::decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype) { typedef typename GrayPixelWrapper<_T_>::channel_type channel_type; typedef typename GrayPixelWrapper<_T_>::pixel_type pixel_type; KIS_ASSERT_RECOVER_RETURN( layer->paintDevice()->colorSpace()->colorModelId() == GrayAColorModelID); QVector pixels(width * height); Q_ASSERT(info.channelMap.contains("G")); dbgFile << "G -> " << info.channelMap["G"]; bool hasAlpha = info.channelMap.contains("A"); dbgFile << "Has Alpha:" << hasAlpha; Imf::FrameBuffer frameBuffer; pixel_type* frameBufferData = (pixels.data()) - xstart - ystart * width; frameBuffer.insert(info.channelMap["G"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->gray, sizeof(pixel_type) * 1, sizeof(pixel_type) * width)); if (hasAlpha) { frameBuffer.insert(info.channelMap["A"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->alpha, sizeof(pixel_type) * 1, sizeof(pixel_type) * width)); } file.setFrameBuffer(frameBuffer); file.readPixels(ystart, height + ystart - 1); pixel_type *srcPtr = pixels.data(); QRect paintRegion(xstart, ystart, width, height); KisSequentialIterator it(layer->paintDevice(), paintRegion); do { if (hasAlpha) { unmultiplyAlpha >(srcPtr); } pixel_type* dstPtr = reinterpret_cast(it.rawData()); dstPtr->gray = srcPtr->gray; dstPtr->alpha = hasAlpha ? srcPtr->alpha : channel_type(1.0); ++srcPtr; } while (it.nextPixel()); } bool recCheckGroup(const ExrGroupLayerInfo& group, QStringList list, int idx1, int idx2) { if (idx1 > idx2) return true; if (group.name == list[idx2]) { return recCheckGroup(*group.parent, list, idx1, idx2 - 1); } return false; } ExrGroupLayerInfo* searchGroup(QList* groups, QStringList list, int idx1, int idx2) { if (idx1 > idx2) { return 0; } // Look for the group for (int i = 0; i < groups->size(); ++i) { if (recCheckGroup(groups->at(i), list, idx1, idx2)) { return &(*groups)[i]; } } // Create the group ExrGroupLayerInfo info; info.name = list.at(idx2); info.parent = searchGroup(groups, list, idx1, idx2 - 1); groups->append(info); return &groups->last(); } QDomDocument EXRConverter::Private::loadExtraLayersInfo(const Imf::Header &header) { const Imf::StringAttribute *layersInfoAttribute = header.findTypedAttribute(EXR_KRITA_LAYERS); if (!layersInfoAttribute) return QDomDocument(); QString layersInfoString = QString::fromUtf8(layersInfoAttribute->value().c_str()); QDomDocument doc; doc.setContent(layersInfoString); return doc; } bool EXRConverter::Private::checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set exrLayerNames) { std::set extraInfoLayers; QDomElement root = doc.documentElement(); KIS_ASSERT_RECOVER(!root.isNull() && root.hasChildNodes()) { return false; }; QDomElement el = root.firstChildElement(); while(!el.isNull()) { KIS_ASSERT_RECOVER(el.hasAttribute(EXR_NAME)) { return false; }; QString layerName = el.attribute(EXR_NAME).toUtf8(); if (layerName != QString(HDR_LAYER)) { extraInfoLayers.insert(el.attribute(EXR_NAME).toUtf8().constData()); } el = el.nextSiblingElement(); } bool result = (extraInfoLayers == exrLayerNames); if (!result) { dbgKrita << "WARINING: Krita EXR extra layers info is inconsistent!"; dbgKrita << ppVar(extraInfoLayers.size()) << ppVar(exrLayerNames.size()); std::set::const_iterator it1 = extraInfoLayers.begin(); std::set::const_iterator it2 = exrLayerNames.begin(); std::set::const_iterator end1 = extraInfoLayers.end(); for (; it1 != end1; ++it1, ++it2) { dbgKrita << it1->c_str() << it2->c_str(); } } return result; } -KisImageBuilder_Result EXRConverter::decode(const QString &filename) +KisImportExportErrorCode EXRConverter::decode(const QString &filename) { - Imf::InputFile file(QFile::encodeName(filename)); + try { + Imf::InputFile file(QFile::encodeName(filename)); - Imath::Box2i dw = file.header().dataWindow(); - Imath::Box2i displayWindow = file.header().displayWindow(); + Imath::Box2i dw = file.header().dataWindow(); + Imath::Box2i displayWindow = file.header().displayWindow(); - int width = dw.max.x - dw.min.x + 1; - int height = dw.max.y - dw.min.y + 1; - int dx = dw.min.x; - int dy = dw.min.y; + int width = dw.max.x - dw.min.x + 1; + int height = dw.max.y - dw.min.y + 1; + int dx = dw.min.x; + int dy = dw.min.y; - // Display the attributes of a file - for (Imf::Header::ConstIterator it = file.header().begin(); - it != file.header().end(); ++it) { - dbgFile << "Attribute: " << it.name() << " type: " << it.attribute().typeName(); - } + // Display the attributes of a file + for (Imf::Header::ConstIterator it = file.header().begin(); + it != file.header().end(); ++it) { + dbgFile << "Attribute: " << it.name() << " type: " << it.attribute().typeName(); + } - // fetch Krita's extra layer info, which might have been stored previously - QDomDocument extraLayersInfo = d->loadExtraLayersInfo(file.header()); + // fetch Krita's extra layer info, which might have been stored previously + QDomDocument extraLayersInfo = d->loadExtraLayersInfo(file.header()); - // Construct the list of LayerInfo + // Construct the list of LayerInfo - QList informationObjects; - QList groups; + QList informationObjects; + QList groups; - ImageType imageType = IT_UNKNOWN; + ImageType imageType = IT_UNKNOWN; - const Imf::ChannelList &channels = file.header().channels(); - std::set layerNames; - channels.layers(layerNames); + const Imf::ChannelList &channels = file.header().channels(); + std::set layerNames; + channels.layers(layerNames); - if (!extraLayersInfo.isNull() && - !d->checkExtraLayersInfoConsistent(extraLayersInfo, layerNames)) { + if (!extraLayersInfo.isNull() && + !d->checkExtraLayersInfoConsistent(extraLayersInfo, layerNames)) { - // it is inconsistent anyway - extraLayersInfo = QDomDocument(); - } + // it is inconsistent anyway + extraLayersInfo = QDomDocument(); + } - // Check if there are A, R, G, B channels - - dbgFile << "Checking for ARGB channels, they can occur in single-layer _or_ multi-layer images:"; - ExrPaintLayerInfo info; - bool topLevelRGBFound = false; - info.name = HDR_LAYER; - - QStringList topLevelChannelNames = QStringList() << "A" << "R" << "G" << "B" - << ".A" << ".R" << ".G" << ".B" - << "A." << "R." << "G." << "B." - << "A." << "R." << "G." << "B." - << ".alpha" << ".red" << ".green" << ".blue"; - - for (Imf::ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) { - const Imf::Channel &channel = i.channel(); - dbgFile << "Channel name = " << i.name() << " type = " << channel.type; - - QString qname = i.name(); - if (topLevelChannelNames.contains(qname)) { - topLevelRGBFound = true; - dbgFile << "Found top-level channel" << qname; - info.channelMap[qname] = qname; - info.updateImageType(imfTypeToKisType(channel.type)); + // Check if there are A, R, G, B channels + + dbgFile << "Checking for ARGB channels, they can occur in single-layer _or_ multi-layer images:"; + ExrPaintLayerInfo info; + bool topLevelRGBFound = false; + info.name = HDR_LAYER; + + QStringList topLevelChannelNames = QStringList() << "A" << "R" << "G" << "B" + << ".A" << ".R" << ".G" << ".B" + << "A." << "R." << "G." << "B." + << "A." << "R." << "G." << "B." + << ".alpha" << ".red" << ".green" << ".blue"; + + for (Imf::ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) { + const Imf::Channel &channel = i.channel(); + dbgFile << "Channel name = " << i.name() << " type = " << channel.type; + + QString qname = i.name(); + if (topLevelChannelNames.contains(qname)) { + topLevelRGBFound = true; + dbgFile << "Found top-level channel" << qname; + info.channelMap[qname] = qname; + info.updateImageType(imfTypeToKisType(channel.type)); + } + // Channel names that don't contain a "." or that contain a + // "." only at the beginning or at the end are not considered + // to be part of any layer. + else if (!qname.contains('.') + || !qname.mid(1).contains('.') + || !qname.left(qname.size() - 1).contains('.')) { + warnFile << "Found a top-level channel that is not part of the rendered image" << qname << ". Krita will not load this channel."; + } } - // Channel names that don't contain a "." or that contain a - // "." only at the beginning or at the end are not considered - // to be part of any layer. - else if (!qname.contains('.') - || !qname.mid(1).contains('.') - || !qname.left(qname.size() - 1).contains('.')) { - warnFile << "Found a top-level channel that is not part of the rendered image" << qname << ". Krita will not load this channel."; + if (topLevelRGBFound) { + dbgFile << "Toplevel layer" << info.name << ":Image type:" << imageType << "Layer type" << info.imageType; + informationObjects.push_back(info); + imageType = info.imageType; } - } - if (topLevelRGBFound) { - dbgFile << "Toplevel layer" << info.name << ":Image type:" << imageType << "Layer type" << info.imageType; - informationObjects.push_back(info); - imageType = info.imageType; - } - dbgFile << "Extra layers:" << layerNames.size(); + dbgFile << "Extra layers:" << layerNames.size(); - for (std::set::const_iterator i = layerNames.begin();i != layerNames.end(); ++i) { + for (std::set::const_iterator i = layerNames.begin();i != layerNames.end(); ++i) { - info = ExrPaintLayerInfo(); + info = ExrPaintLayerInfo(); - dbgFile << "layer name = " << i->c_str(); - info.name = i->c_str(); - Imf::ChannelList::ConstIterator layerBegin, layerEnd; - channels.channelsInLayer(*i, layerBegin, layerEnd); - for (Imf::ChannelList::ConstIterator j = layerBegin; - j != layerEnd; ++j) { - const Imf::Channel &channel = j.channel(); + dbgFile << "layer name = " << i->c_str(); + info.name = i->c_str(); + Imf::ChannelList::ConstIterator layerBegin, layerEnd; + channels.channelsInLayer(*i, layerBegin, layerEnd); + for (Imf::ChannelList::ConstIterator j = layerBegin; + j != layerEnd; ++j) { + const Imf::Channel &channel = j.channel(); - info.updateImageType(imfTypeToKisType(channel.type)); + info.updateImageType(imfTypeToKisType(channel.type)); - QString qname = j.name(); - QStringList list = qname.split('.'); - QString layersuffix = list.last(); + QString qname = j.name(); + QStringList list = qname.split('.'); + QString layersuffix = list.last(); - dbgFile << "\tchannel " << j.name() << "suffix" << layersuffix << " type = " << channel.type; + dbgFile << "\tchannel " << j.name() << "suffix" << layersuffix << " type = " << channel.type; - // Nuke writes the channels for sublayers as .red instead of .R, so convert those. - // See https://bugs.kde.org/show_bug.cgi?id=393771 - if (topLevelChannelNames.contains("." + layersuffix)) { - layersuffix = layersuffix.at(0).toUpper(); - } - dbgFile << "\t\tsuffix" << layersuffix; + // Nuke writes the channels for sublayers as .red instead of .R, so convert those. + // See https://bugs.kde.org/show_bug.cgi?id=393771 + if (topLevelChannelNames.contains("." + layersuffix)) { + layersuffix = layersuffix.at(0).toUpper(); + } + dbgFile << "\t\tsuffix" << layersuffix; - if (list.size() > 1) { - info.name = list[list.size()-2]; - info.parent = searchGroup(&groups, list, 0, list.size() - 3); - } + if (list.size() > 1) { + info.name = list[list.size()-2]; + info.parent = searchGroup(&groups, list, 0, list.size() - 3); + } - info.channelMap[layersuffix] = qname; - } + info.channelMap[layersuffix] = qname; + } - if (info.imageType != IT_UNKNOWN && info.imageType != IT_UNSUPPORTED) { - informationObjects.push_back(info); - if (imageType < info.imageType) { - imageType = info.imageType; + if (info.imageType != IT_UNKNOWN && info.imageType != IT_UNSUPPORTED) { + informationObjects.push_back(info); + if (imageType < info.imageType) { + imageType = info.imageType; + } } } - } - dbgFile << "File has" << informationObjects.size() << "layer(s)"; - - // Set the colorspaces - for (int i = 0; i < informationObjects.size(); ++i) { - ExrPaintLayerInfo& info = informationObjects[i]; - QString modelId; - - if (info.channelMap.size() == 1) { - modelId = GrayAColorModelID.id(); - QString key = info.channelMap.begin().key(); - if (key != "G") { - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(key, "G")); - QString channel = info.channelMap.begin().value(); - info.channelMap.clear(); - info.channelMap["G"] = channel; + dbgFile << "File has" << informationObjects.size() << "layer(s)"; + + // Set the colorspaces + for (int i = 0; i < informationObjects.size(); ++i) { + ExrPaintLayerInfo& info = informationObjects[i]; + QString modelId; + + if (info.channelMap.size() == 1) { + modelId = GrayAColorModelID.id(); + QString key = info.channelMap.begin().key(); + if (key != "G") { + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(key, "G")); + QString channel = info.channelMap.begin().value(); + info.channelMap.clear(); + info.channelMap["G"] = channel; + } } - } - else if (info.channelMap.size() == 2) { - modelId = GrayAColorModelID.id(); + else if (info.channelMap.size() == 2) { + modelId = GrayAColorModelID.id(); - QMap::const_iterator it = info.channelMap.constBegin(); - QMap::const_iterator end = info.channelMap.constEnd(); + QMap::const_iterator it = info.channelMap.constBegin(); + QMap::const_iterator end = info.channelMap.constEnd(); - QString failingChannelKey; + QString failingChannelKey; - for (; it != end; ++it) { - if (it.key() != "G" && it.key() != "A") { - failingChannelKey = it.key(); - break; + for (; it != end; ++it) { + if (it.key() != "G" && it.key() != "A") { + failingChannelKey = it.key(); + break; + } } - } - info.remappedChannels.push_back( - ExrPaintLayerInfo::Remap(failingChannelKey, "G")); + info.remappedChannels.push_back( + ExrPaintLayerInfo::Remap(failingChannelKey, "G")); - QString failingChannelValue = info.channelMap[failingChannelKey]; - info.channelMap.remove(failingChannelKey); - info.channelMap["G"] = failingChannelValue; + QString failingChannelValue = info.channelMap[failingChannelKey]; + info.channelMap.remove(failingChannelKey); + info.channelMap["G"] = failingChannelValue; - } - else if (info.channelMap.size() == 3 || info.channelMap.size() == 4) { - - if (info.channelMap.contains("R") && info.channelMap.contains("G") && info.channelMap.contains("B")) { - modelId = RGBAColorModelID.id(); } - else if (info.channelMap.contains("X") && info.channelMap.contains("Y") && info.channelMap.contains("Z")) { - modelId = XYZAColorModelID.id(); - QMap newChannelMap; - if (info.channelMap.contains("W")) { - newChannelMap["A"] = info.channelMap["W"]; - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("W", "A")); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("X", "X")); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Y", "Y")); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Z", "Z")); - } else if (info.channelMap.contains("A")) { - newChannelMap["A"] = info.channelMap["A"]; + else if (info.channelMap.size() == 3 || info.channelMap.size() == 4) { + + if (info.channelMap.contains("R") && info.channelMap.contains("G") && info.channelMap.contains("B")) { + modelId = RGBAColorModelID.id(); } - // The decode function expect R, G, B in the channel map - newChannelMap["B"] = info.channelMap["X"]; - newChannelMap["G"] = info.channelMap["Y"]; - newChannelMap["R"] = info.channelMap["Z"]; - info.channelMap = newChannelMap; - } - else { - modelId = RGBAColorModelID.id(); - QMap newChannelMap; - QMap::iterator it = info.channelMap.begin(); - newChannelMap["R"] = it.value(); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "R")); - ++it; - newChannelMap["G"] = it.value(); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "G")); - ++it; - newChannelMap["B"] = it.value(); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "B")); - if (info.channelMap.size() == 4) { - ++it; - newChannelMap["A"] = it.value(); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "A")); + else if (info.channelMap.contains("X") && info.channelMap.contains("Y") && info.channelMap.contains("Z")) { + modelId = XYZAColorModelID.id(); + QMap newChannelMap; + if (info.channelMap.contains("W")) { + newChannelMap["A"] = info.channelMap["W"]; + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("W", "A")); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("X", "X")); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Y", "Y")); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Z", "Z")); + } else if (info.channelMap.contains("A")) { + newChannelMap["A"] = info.channelMap["A"]; + } + // The decode function expect R, G, B in the channel map + newChannelMap["B"] = info.channelMap["X"]; + newChannelMap["G"] = info.channelMap["Y"]; + newChannelMap["R"] = info.channelMap["Z"]; + info.channelMap = newChannelMap; } + else { + modelId = RGBAColorModelID.id(); + QMap newChannelMap; + QMap::iterator it = info.channelMap.begin(); + newChannelMap["R"] = it.value(); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "R")); + ++it; + newChannelMap["G"] = it.value(); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "G")); + ++it; + newChannelMap["B"] = it.value(); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "B")); + if (info.channelMap.size() == 4) { + ++it; + newChannelMap["A"] = it.value(); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "A")); + } - info.channelMap = newChannelMap; + info.channelMap = newChannelMap; + } + } + else { + dbgFile << info.name << "has" << info.channelMap.size() << "channels, and we don't know what to do."; + } + if (!modelId.isEmpty()) { + info.colorSpace = kisTypeToColorSpace(modelId, info.imageType); } } - else { - dbgFile << info.name << "has" << info.channelMap.size() << "channels, and we don't know what to do."; - } - if (!modelId.isEmpty()) { - info.colorSpace = kisTypeToColorSpace(modelId, info.imageType); - } - } - // Get colorspace - dbgFile << "Image type = " << imageType; - const KoColorSpace* colorSpace = kisTypeToColorSpace(RGBAColorModelID.id(), imageType); + // Get colorspace + dbgFile << "Image type = " << imageType; + const KoColorSpace* colorSpace = kisTypeToColorSpace(RGBAColorModelID.id(), imageType); - if (!colorSpace) return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; - dbgFile << "Colorspace: " << colorSpace->name(); + if (!colorSpace) return ImportExportCodes::FormatColorSpaceUnsupported; + dbgFile << "Colorspace: " << colorSpace->name(); - // Set the colorspace on all groups - for (int i = 0; i < groups.size(); ++i) { - ExrGroupLayerInfo& info = groups[i]; - info.colorSpace = colorSpace; - } + // Set the colorspace on all groups + for (int i = 0; i < groups.size(); ++i) { + ExrGroupLayerInfo& info = groups[i]; + info.colorSpace = colorSpace; + } - // Create the image - // Make sure the created image is the same size as the displayWindow since - // the dataWindow can be cropped in some cases. - int displayWidth = displayWindow.max.x - displayWindow.min.x + 1; - int displayHeight = displayWindow.max.y - displayWindow.min.y + 1; - d->image = new KisImage(d->doc->createUndoStore(), displayWidth, displayHeight, colorSpace, ""); + // Create the image + // Make sure the created image is the same size as the displayWindow since + // the dataWindow can be cropped in some cases. + int displayWidth = displayWindow.max.x - displayWindow.min.x + 1; + int displayHeight = displayWindow.max.y - displayWindow.min.y + 1; + d->image = new KisImage(d->doc->createUndoStore(), displayWidth, displayHeight, colorSpace, ""); - if (!d->image) { - return KisImageBuilder_RESULT_FAILURE; - } + if (!d->image) { + return ImportExportCodes::Failure; + } - /** - * EXR semi-transparent images are expected to be rendered on - * black to ensure correctness of the light model - */ - d->image->setDefaultProjectionColor(KoColor(Qt::black, colorSpace)); - - // Create group layers - for (int i = 0; i < groups.size(); ++i) { - ExrGroupLayerInfo& info = groups[i]; - Q_ASSERT(info.parent == 0 || info.parent->groupLayer); - KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer(); - info.groupLayer = new KisGroupLayer(d->image, info.name, OPACITY_OPAQUE_U8); - d->image->addNode(info.groupLayer, groupLayerParent); - } + /** + * EXR semi-transparent images are expected to be rendered on + * black to ensure correctness of the light model + */ + d->image->setDefaultProjectionColor(KoColor(Qt::black, colorSpace)); - // Load the layers - for (int i = informationObjects.size() - 1; i >= 0; --i) { - ExrPaintLayerInfo& info = informationObjects[i]; - if (info.colorSpace) { - dbgFile << "Decoding " << info.name << " with " << info.channelMap.size() << " channels, and color space " << info.colorSpace->id(); - KisPaintLayerSP layer = new KisPaintLayer(d->image, info.name, OPACITY_OPAQUE_U8, info.colorSpace); + // Create group layers + for (int i = 0; i < groups.size(); ++i) { + ExrGroupLayerInfo& info = groups[i]; + Q_ASSERT(info.parent == 0 || info.parent->groupLayer); + KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer(); + info.groupLayer = new KisGroupLayer(d->image, info.name, OPACITY_OPAQUE_U8); + d->image->addNode(info.groupLayer, groupLayerParent); + } - layer->setCompositeOpId(COMPOSITE_OVER); + // Load the layers + for (int i = informationObjects.size() - 1; i >= 0; --i) { + ExrPaintLayerInfo& info = informationObjects[i]; + if (info.colorSpace) { + dbgFile << "Decoding " << info.name << " with " << info.channelMap.size() << " channels, and color space " << info.colorSpace->id(); + KisPaintLayerSP layer = new KisPaintLayer(d->image, info.name, OPACITY_OPAQUE_U8, info.colorSpace); - if (!layer) { - return KisImageBuilder_RESULT_FAILURE; - } + if (!layer) { + return ImportExportCodes::Failure; + } - switch (info.channelMap.size()) { - case 1: - case 2: - // Decode the data - switch (info.imageType) { - case IT_FLOAT16: - d->decodeData1(file, info, layer, width, dx, dy, height, Imf::HALF); + layer->setCompositeOpId(COMPOSITE_OVER); + + switch (info.channelMap.size()) { + case 1: + case 2: + // Decode the data + switch (info.imageType) { + case IT_FLOAT16: + d->decodeData1(file, info, layer, width, dx, dy, height, Imf::HALF); + break; + case IT_FLOAT32: + d->decodeData1(file, info, layer, width, dx, dy, height, Imf::FLOAT); + break; + case IT_UNKNOWN: + case IT_UNSUPPORTED: + qFatal("Impossible error"); + } break; - case IT_FLOAT32: - d->decodeData1(file, info, layer, width, dx, dy, height, Imf::FLOAT); + case 3: + case 4: + // Decode the data + switch (info.imageType) { + case IT_FLOAT16: + d->decodeData4(file, info, layer, width, dx, dy, height, Imf::HALF); + break; + case IT_FLOAT32: + d->decodeData4(file, info, layer, width, dx, dy, height, Imf::FLOAT); + break; + case IT_UNKNOWN: + case IT_UNSUPPORTED: + qFatal("Impossible error"); + } break; - case IT_UNKNOWN: - case IT_UNSUPPORTED: - qFatal("Impossible error"); + default: + qFatal("Invalid number of channels: %i", info.channelMap.size()); } - break; - case 3: - case 4: - // Decode the data - switch (info.imageType) { - case IT_FLOAT16: - d->decodeData4(file, info, layer, width, dx, dy, height, Imf::HALF); - break; - case IT_FLOAT32: - d->decodeData4(file, info, layer, width, dx, dy, height, Imf::FLOAT); - break; - case IT_UNKNOWN: - case IT_UNSUPPORTED: - qFatal("Impossible error"); + // Check if should set the channels + if (!info.remappedChannels.isEmpty()) { + QList values; + Q_FOREACH (const ExrPaintLayerInfo::Remap& remap, info.remappedChannels) { + QMap map; + map["original"] = KisMetaData::Value(remap.original); + map["current"] = KisMetaData::Value(remap.current); + values.append(map); + } + layer->metaData()->addEntry(KisMetaData::Entry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap", values)); } - break; - default: - qFatal("Invalid number of channels: %i", info.channelMap.size()); + // Add the layer + KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer(); + d->image->addNode(layer, groupLayerParent); + } else { + dbgFile << "No decoding " << info.name << " with " << info.channelMap.size() << " channels, and lack of a color space"; } - // Check if should set the channels - if (!info.remappedChannels.isEmpty()) { - QList values; - Q_FOREACH (const ExrPaintLayerInfo::Remap& remap, info.remappedChannels) { - QMap map; - map["original"] = KisMetaData::Value(remap.original); - map["current"] = KisMetaData::Value(remap.current); - values.append(map); - } - layer->metaData()->addEntry(KisMetaData::Entry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap", values)); + } + // Set projectionColor to opaque + d->image->setDefaultProjectionColor(KoColor(Qt::transparent, colorSpace)); + + // After reading the image, notify the user about changed alpha. + if (d->alphaWasModified) { + QString msg = + i18nc("@info", + "The image contains pixels with zero alpha channel and non-zero " + "color channels. Krita has modified those pixels to have " + "at least some alpha. The initial values will not " + "be reverted on saving the image back." + "

" + "This will hardly make any visual difference just keep it in mind."); + if (d->showNotifications) { + QMessageBox::information(0, i18nc("@title:window", "EXR image has been modified"), msg); + } else { + warnKrita << "WARNING:" << msg; } - // Add the layer - KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer(); - d->image->addNode(layer, groupLayerParent); - } else { - dbgFile << "No decoding " << info.name << " with " << info.channelMap.size() << " channels, and lack of a color space"; } - } - // Set projectionColor to opaque - d->image->setDefaultProjectionColor(KoColor(Qt::transparent, colorSpace)); - - // After reading the image, notify the user about changed alpha. - if (d->alphaWasModified) { - QString msg = - i18nc("@info", - "The image contains pixels with zero alpha channel and non-zero " - "color channels. Krita has modified those pixels to have " - "at least some alpha. The initial values will not " - "be reverted on saving the image back." - "

" - "This will hardly make any visual difference just keep it in mind."); - if (d->showNotifications) { - QMessageBox::information(0, i18nc("@title:window", "EXR image has been modified"), msg); - } else { - warnKrita << "WARNING:" << msg; + + if (!extraLayersInfo.isNull()) { + KisExrLayersSorter sorter(extraLayersInfo, d->image); } - } - if (!extraLayersInfo.isNull()) { - KisExrLayersSorter sorter(extraLayersInfo, d->image); + return ImportExportCodes::OK; + + } catch (std::exception &e) { + dbgFile << "Error while reading from the exr file: " << e.what(); + + if (!KisImportExportAdditionalChecks::doesFileExist(filename)) { + return ImportExportCodes::FileNotExist; + } else if(!KisImportExportAdditionalChecks::isFileReadable(filename)) { + return ImportExportCodes::NoAccessToRead; + } else { + return ImportExportCodes::ErrorWhileReading; + } } - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -KisImageBuilder_Result EXRConverter::buildImage(const QString &filename) +KisImportExportErrorCode EXRConverter::buildImage(const QString &filename) { return decode(filename); } KisImageSP EXRConverter::image() { return d->image; } QString EXRConverter::errorMessage() const { return d->errorMessage; } template struct ExrPixel_ { _T_ data[size]; }; class Encoder { public: virtual ~Encoder() {} virtual void prepareFrameBuffer(Imf::FrameBuffer*, int line) = 0; virtual void encodeData(int line) = 0; }; template class EncoderImpl : public Encoder { public: EncoderImpl(Imf::OutputFile* _file, const ExrPaintLayerSaveInfo* _info, int width) : file(_file), info(_info), pixels(width), m_width(width) {} ~EncoderImpl() override {} void prepareFrameBuffer(Imf::FrameBuffer*, int line) override; void encodeData(int line) override; private: typedef ExrPixel_<_T_, size> ExrPixel; Imf::OutputFile* file; const ExrPaintLayerSaveInfo* info; QVector pixels; int m_width; }; template void EncoderImpl<_T_, size, alphaPos>::prepareFrameBuffer(Imf::FrameBuffer* frameBuffer, int line) { int xstart = 0; int ystart = 0; ExrPixel* frameBufferData = (pixels.data()) - xstart - (ystart + line) * m_width; for (int k = 0; k < size; ++k) { frameBuffer->insert(info->channels[k].toUtf8(), Imf::Slice(info->pixelType, (char *) &frameBufferData->data[k], sizeof(ExrPixel) * 1, sizeof(ExrPixel) * m_width)); } } template void EncoderImpl<_T_, size, alphaPos>::encodeData(int line) { ExrPixel *rgba = pixels.data(); KisHLineConstIteratorSP it = info->layerDevice->createHLineConstIteratorNG(0, line, m_width); do { const _T_* dst = reinterpret_cast < const _T_* >(it->oldRawData()); for (int i = 0; i < size; ++i) { rgba->data[i] = dst[i]; } if (alphaPos != -1) { multiplyAlpha<_T_, ExrPixel, size, alphaPos>(rgba); } ++rgba; } while (it->nextPixel()); } Encoder* encoder(Imf::OutputFile& file, const ExrPaintLayerSaveInfo& info, int width) { dbgFile << "Create encoder for" << info.name << info.channels << info.layerDevice->colorSpace()->channelCount(); switch (info.layerDevice->colorSpace()->channelCount()) { case 1: { if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::HALF); return new EncoderImpl < half, 1, -1 > (&file, &info, width); } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::FLOAT); return new EncoderImpl < float, 1, -1 > (&file, &info, width); } break; } case 2: { if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::HALF); return new EncoderImpl(&file, &info, width); } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::FLOAT); return new EncoderImpl(&file, &info, width); } break; } case 4: { if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::HALF); return new EncoderImpl(&file, &info, width); } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::FLOAT); return new EncoderImpl(&file, &info, width); } break; } default: qFatal("Impossible error"); } return 0; } void encodeData(Imf::OutputFile& file, const QList& informationObjects, int width, int height) { QList encoders; Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) { encoders.push_back(encoder(file, info, width)); } for (int y = 0; y < height; ++y) { Imf::FrameBuffer frameBuffer; Q_FOREACH (Encoder* encoder, encoders) { encoder->prepareFrameBuffer(&frameBuffer, y); } file.setFrameBuffer(frameBuffer); Q_FOREACH (Encoder* encoder, encoders) { encoder->encodeData(y); } file.writePixels(1); } qDeleteAll(encoders); } KisPaintDeviceSP wrapLayerDevice(KisPaintDeviceSP device) { const KoColorSpace *cs = device->colorSpace(); if (cs->colorDepthId() != Float16BitsColorDepthID && cs->colorDepthId() != Float32BitsColorDepthID) { cs = KoColorSpaceRegistry::instance()->colorSpace( cs->colorModelId() == GrayAColorModelID ? GrayAColorModelID.id() : RGBAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (cs->colorModelId() != GrayColorModelID && cs->colorModelId() != RGBAColorModelID) { cs = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), cs->colorDepthId().id()); } if (*cs != *device->colorSpace()) { device = new KisPaintDevice(*device); device->convertTo(cs); } return device; } -KisImageBuilder_Result EXRConverter::buildFile(const QString &filename, KisPaintLayerSP layer) +KisImportExportErrorCode EXRConverter::buildFile(const QString &filename, KisPaintLayerSP layer) { - if (!layer) - return KisImageBuilder_RESULT_INVALID_ARG; + KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError); KisImageSP image = layer->image(); - if (!image) - return KisImageBuilder_RESULT_EMPTY; + KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError); + // Make the header qint32 height = image->height(); qint32 width = image->width(); Imf::Header header(width, height); ExrPaintLayerSaveInfo info; info.layer = layer; info.layerDevice = wrapLayerDevice(layer->paintDevice()); - Imf::PixelType pixelType = Imf::NUM_PIXELTYPES; if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { pixelType = Imf::HALF; } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { pixelType = Imf::FLOAT; } - + ENTER_FUNCTION() << "(4)"; header.channels().insert("R", Imf::Channel(pixelType)); header.channels().insert("G", Imf::Channel(pixelType)); header.channels().insert("B", Imf::Channel(pixelType)); header.channels().insert("A", Imf::Channel(pixelType)); info.channels.push_back("R"); info.channels.push_back("G"); info.channels.push_back("B"); info.channels.push_back("A"); info.pixelType = pixelType; + ENTER_FUNCTION() << "(5)"; // Open file for writing - Imf::OutputFile file(QFile::encodeName(filename), header); + try { + Imf::OutputFile file(QFile::encodeName(filename), header); - QList informationObjects; - informationObjects.push_back(info); + QList informationObjects; + informationObjects.push_back(info); + encodeData(file, informationObjects, width, height); + return ImportExportCodes::OK; - encodeData(file, informationObjects, width, height); + } catch(std::exception &e) { + dbgFile << "Exception while writing to exr file: " << e.what(); + if (!KisImportExportAdditionalChecks::isFileWritable(QFile::encodeName(filename))) { + return ImportExportCodes::NoAccessToWrite; + } + return ImportExportCodes::Failure; + } - return KisImageBuilder_RESULT_OK; } QString remap(const QMap& current2original, const QString& current) { if (current2original.contains(current)) { return current2original[current]; } return current; } void EXRConverter::Private::makeLayerNamesUnique(QList& informationObjects) { typedef QMultiMap::iterator> NamesMap; NamesMap namesMap; { QList::iterator it = informationObjects.begin(); QList::iterator end = informationObjects.end(); for (; it != end; ++it) { namesMap.insert(it->name, it); } } Q_FOREACH (const QString &key, namesMap.keys()) { if (namesMap.count(key) > 1) { KIS_ASSERT_RECOVER(key.endsWith(".")) { continue; } QString strippedName = key.left(key.size() - 1); // trim the ending dot int nameCounter = 0; NamesMap::iterator it = namesMap.find(key); NamesMap::iterator end = namesMap.end(); for (; it != end; ++it) { QString newName = QString("%1_%2.") .arg(strippedName) .arg(nameCounter++); it.value()->name = newName; QList::iterator channelsIt = it.value()->channels.begin(); QList::iterator channelsEnd = it.value()->channels.end(); for (; channelsIt != channelsEnd; ++channelsIt) { channelsIt->replace(key, newName); } } } } } void EXRConverter::Private::recBuildPaintLayerSaveInfo(QList& informationObjects, const QString& name, KisGroupLayerSP parent) { QSet layersNotSaved; for (uint i = 0; i < parent->childCount(); ++i) { KisNodeSP node = parent->at(i); if (KisPaintLayerSP paintLayer = dynamic_cast(node.data())) { QMap current2original; if (paintLayer->metaData()->containsEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap")) { const KisMetaData::Entry& entry = paintLayer->metaData()->getEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap"); QList< KisMetaData::Value> values = entry.value().asArray(); Q_FOREACH (const KisMetaData::Value& value, values) { QMap map = value.asStructure(); if (map.contains("original") && map.contains("current")) { current2original[map["current"].toString()] = map["original"].toString(); } } } ExrPaintLayerSaveInfo info; info.name = name + paintLayer->name() + '.'; info.layer = paintLayer; info.layerDevice = wrapLayerDevice(paintLayer->paintDevice()); if (info.name == QString(HDR_LAYER) + ".") { info.channels.push_back("R"); info.channels.push_back("G"); info.channels.push_back("B"); info.channels.push_back("A"); } else { if (paintLayer->colorSpace()->colorModelId() == RGBAColorModelID) { info.channels.push_back(info.name + remap(current2original, "R")); info.channels.push_back(info.name + remap(current2original, "G")); info.channels.push_back(info.name + remap(current2original, "B")); info.channels.push_back(info.name + remap(current2original, "A")); } else if (paintLayer->colorSpace()->colorModelId() == GrayAColorModelID) { info.channels.push_back(info.name + remap(current2original, "G")); info.channels.push_back(info.name + remap(current2original, "A")); } else if (paintLayer->colorSpace()->colorModelId() == GrayColorModelID) { info.channels.push_back(info.name + remap(current2original, "G")); } else if (paintLayer->colorSpace()->colorModelId() == XYZAColorModelID) { info.channels.push_back(info.name + remap(current2original, "X")); info.channels.push_back(info.name + remap(current2original, "Y")); info.channels.push_back(info.name + remap(current2original, "Z")); info.channels.push_back(info.name + remap(current2original, "A")); } } if (paintLayer->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { info.pixelType = Imf::HALF; } else if (paintLayer->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { info.pixelType = Imf::FLOAT; } else { info.pixelType = Imf::NUM_PIXELTYPES; } if (info.pixelType < Imf::NUM_PIXELTYPES) { dbgFile << "Going to save layer" << info.name; informationObjects.push_back(info); } else { warnFile << "Will not save layer" << info.name; layersNotSaved << node; } } else if (KisGroupLayerSP groupLayer = dynamic_cast(node.data())) { recBuildPaintLayerSaveInfo(informationObjects, name + groupLayer->name() + '.', groupLayer); } else { /** * The EXR can store paint and group layers only. The rest will * go to /dev/null :( */ layersNotSaved.insert(node); } } if (!layersNotSaved.isEmpty()) { reportLayersNotSaved(layersNotSaved); } } void EXRConverter::Private::reportLayersNotSaved(const QSet &layersNotSaved) { QString layersList; QTextStream textStream(&layersList); textStream.setCodec("UTF-8"); Q_FOREACH (KisNodeSP node, layersNotSaved) { textStream << "
  • " << i18nc("@item:unsupported-node-message", "%1 (type: \"%2\")", node->name(), node->metaObject()->className()) << "
  • "; } QString msg = i18nc("@info", "

    The following layers have a type that is not supported by EXR format:

    " "
      %1

    " "

    these layers have not been saved to the final EXR file

    ", layersList); errorMessage = msg; } QString EXRConverter::Private::fetchExtraLayersInfo(QList& informationObjects) { KIS_ASSERT_RECOVER_NOOP(!informationObjects.isEmpty()); if (informationObjects.size() == 1 && informationObjects[0].name == QString(HDR_LAYER) + ".") { return QString(); } QDomDocument doc("krita-extra-layers-info"); doc.appendChild(doc.createElement("root")); QDomElement rootElement = doc.documentElement(); for (int i = 0; i < informationObjects.size(); i++) { ExrPaintLayerSaveInfo &info = informationObjects[i]; quint32 unused; KisSaveXmlVisitor visitor(doc, rootElement, unused, QString(), false); QDomElement el = visitor.savePaintLayerAttributes(info.layer.data(), doc); // cut the ending '.' QString strippedName = info.name.left(info.name.size() - 1); el.setAttribute(EXR_NAME, strippedName); rootElement.appendChild(el); } return doc.toString(); } -KisImageBuilder_Result EXRConverter::buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten) +KisImportExportErrorCode EXRConverter::buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten) { - if (!layer) - return KisImageBuilder_RESULT_INVALID_ARG; + KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError); KisImageSP image = layer->image(); - if (!image) - return KisImageBuilder_RESULT_EMPTY; - + KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError); qint32 height = image->height(); qint32 width = image->width(); Imf::Header header(width, height); if (flatten) { KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd); return buildFile(filename, l); } else { - QList informationObjects; d->recBuildPaintLayerSaveInfo(informationObjects, "", layer); if(informationObjects.isEmpty()) { - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } - d->makeLayerNamesUnique(informationObjects); QByteArray extraLayersInfo = d->fetchExtraLayersInfo(informationObjects).toUtf8(); if (!extraLayersInfo.isNull()) { header.insert(EXR_KRITA_LAYERS, Imf::StringAttribute(extraLayersInfo.constData())); } dbgFile << informationObjects.size() << " layers to save"; - Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) { if (info.pixelType < Imf::NUM_PIXELTYPES) { Q_FOREACH (const QString& channel, info.channels) { dbgFile << channel << " " << info.pixelType; header.channels().insert(channel.toUtf8().data(), Imf::Channel(info.pixelType)); } } } // Open file for writing - Imf::OutputFile file(QFile::encodeName(filename), header); + try { + Imf::OutputFile file(QFile::encodeName(filename), header); + encodeData(file, informationObjects, width, height); + return ImportExportCodes::OK; + } catch(std::exception &e) { + dbgFile << "Exception while writing to exr file: " << e.what(); + if (!KisImportExportAdditionalChecks::isFileWritable(QFile::encodeName(filename))) { + return ImportExportCodes::NoAccessToWrite; + } + return ImportExportCodes::ErrorWhileWriting; + } - encodeData(file, informationObjects, width, height); - return KisImageBuilder_RESULT_OK; } } void EXRConverter::cancel() { warnKrita << "WARNING: Cancelling of an EXR loading is not supported!"; } diff --git a/plugins/impex/exr/exr_converter.h b/plugins/impex/exr/exr_converter.h index 6db6795339..6a11120e02 100644 --- a/plugins/impex/exr/exr_converter.h +++ b/plugins/impex/exr/exr_converter.h @@ -1,56 +1,57 @@ /* * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _EXR_CONVERTER_H_ #define _EXR_CONVERTER_H_ #include #include #include "kis_types.h" -#include +#include + class KisDocument; class EXRConverter : public QObject { Q_OBJECT public: EXRConverter(KisDocument *doc, bool showNotifications); ~EXRConverter() override; public: - KisImageBuilder_Result buildImage(const QString &filename); - KisImageBuilder_Result buildFile(const QString &filename, KisPaintLayerSP layer); - KisImageBuilder_Result buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten=false); + KisImportExportErrorCode buildImage(const QString &filename); + KisImportExportErrorCode buildFile(const QString &filename, KisPaintLayerSP layer); + KisImportExportErrorCode buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten=false); /** * Retrieve the constructed image */ KisImageSP image(); QString errorMessage() const; private: - KisImageBuilder_Result decode(const QString &filename); + KisImportExportErrorCode decode(const QString &filename); public Q_SLOTS: virtual void cancel(); private: struct Private; const QScopedPointer d; }; #endif diff --git a/plugins/impex/exr/exr_export.cc b/plugins/impex/exr/exr_export.cc index fd23fe0ad4..6c60865aec 100644 --- a/plugins/impex/exr/exr_export.cc +++ b/plugins/impex/exr/exr_export.cc @@ -1,159 +1,127 @@ /* * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "exr_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "exr_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_exr_export.json", registerPlugin();) EXRExport::EXRExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } EXRExport::~EXRExport() { } KisPropertiesConfigurationSP EXRExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("flatten", false); return cfg; } KisConfigWidget *EXRExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisWdgOptionsExr(parent); } -KisImportExportFilter::ConversionStatus EXRExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode EXRExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration) { Q_ASSERT(document); Q_ASSERT(configuration); KisImageSP image = document->savingImage(); Q_ASSERT(image); EXRConverter exrConverter(document, !batchMode()); - KisImageBuilder_Result res; + KisImportExportErrorCode res; if (configuration && configuration->getBool("flatten")) { res = exrConverter.buildFile(filename(), image->rootLayer(), true); } else { res = exrConverter.buildFile(filename(), image->rootLayer()); } dbgFile << " Result =" << res; - switch (res) { - case KisImageBuilder_RESULT_INVALID_ARG: - document->setErrorMessage(i18n("This layer cannot be saved to EXR.")); - return KisImportExportFilter::WrongFormat; - - case KisImageBuilder_RESULT_EMPTY: - document->setErrorMessage(i18n("The layer does not have an image associated with it.")); - return KisImportExportFilter::WrongFormat; - - case KisImageBuilder_RESULT_NO_URI: - document->setErrorMessage(i18n("The filename is empty.")); - return KisImportExportFilter::CreationError; - - case KisImageBuilder_RESULT_NOT_LOCAL: - document->setErrorMessage(i18n("EXR images cannot be saved remotely.")); - return KisImportExportFilter::InternalError; - - case KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE: - document->setErrorMessage(i18n("Colorspace not supported: EXR images must be 16 or 32 bits floating point RGB.")); - return KisImportExportFilter::WrongFormat; - - case KisImageBuilder_RESULT_OK: - if (!exrConverter.errorMessage().isNull()) { - document->setErrorMessage(exrConverter.errorMessage()); - } - return KisImportExportFilter::OK; - default: - break; - } - - document->setErrorMessage(i18n("Internal Error")); - return KisImportExportFilter::InternalError; - + return res; } void EXRExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Float16BitsColorDepthID) << QPair(RGBAColorModelID, Float32BitsColorDepthID) << QPair(GrayAColorModelID, Float16BitsColorDepthID) << QPair(GrayAColorModelID, Float32BitsColorDepthID) << QPair(GrayColorModelID, Float16BitsColorDepthID) << QPair(GrayColorModelID, Float32BitsColorDepthID) << QPair(XYZAColorModelID, Float16BitsColorDepthID) << QPair(XYZAColorModelID, Float32BitsColorDepthID); addSupportedColorModels(supportedColorModels, "EXR"); } void KisWdgOptionsExr::setConfiguration(const KisPropertiesConfigurationSP cfg) { chkFlatten->setChecked(cfg->getBool("flatten", false)); } KisPropertiesConfigurationSP KisWdgOptionsExr::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("flatten", chkFlatten->isChecked()); return cfg; } #include diff --git a/plugins/impex/exr/exr_export.h b/plugins/impex/exr/exr_export.h index 11a501ea19..a95034e9fb 100644 --- a/plugins/impex/exr/exr_export.h +++ b/plugins/impex/exr/exr_export.h @@ -1,59 +1,59 @@ /* * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _EXR_EXPORT_H_ #define _EXR_EXPORT_H_ #include #include #include #include "ui_exr_export_widget.h" class KisWdgOptionsExr : public KisConfigWidget, public Ui::ExrExportWidget { Q_OBJECT public: KisWdgOptionsExr(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); } void setConfiguration(const KisPropertiesConfigurationSP cfg) override; KisPropertiesConfigurationSP configuration() const override; }; class EXRExport : public KisImportExportFilter { Q_OBJECT public: EXRExport(QObject *parent, const QVariantList &); ~EXRExport() override; bool supportsIO() const override { return false; } - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/exr/exr_import.cc b/plugins/impex/exr/exr_import.cc index baae10dbd5..a6926dd6bc 100644 --- a/plugins/impex/exr/exr_import.cc +++ b/plugins/impex/exr/exr_import.cc @@ -1,83 +1,53 @@ /* * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "exr_import.h" #include #include #include #include #include #include "exr_converter.h" K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_exr_import.json", registerPlugin();) exrImport::exrImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } exrImport::~exrImport() { } -KisImportExportFilter::ConversionStatus exrImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode exrImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) { EXRConverter ib(document, !batchMode()); - - switch (ib.buildImage(filename())) { - case KisImageBuilder_RESULT_UNSUPPORTED: - case KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE: - document->setErrorMessage(i18n("Krita does not support this type of EXR file.")); - return KisImportExportFilter::NotImplemented; - - case KisImageBuilder_RESULT_INVALID_ARG: - document->setErrorMessage(i18n("This is not an EXR file.")); - return KisImportExportFilter::BadMimeType; - - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_LOCAL: - document->setErrorMessage(i18n("The EXR file does not exist.")); - return KisImportExportFilter::FileNotFound; - - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - document->setErrorMessage(i18n("The EXR is corrupted.")); - return KisImportExportFilter::ParsingError; - - case KisImageBuilder_RESULT_FAILURE: - document->setErrorMessage(i18n("Krita could not create a new image.")); - return KisImportExportFilter::InternalError; - - case KisImageBuilder_RESULT_OK: - Q_ASSERT(ib.image()); - document -> setCurrentImage(ib.image()); - return KisImportExportFilter::OK; - - default: - break; + KisImportExportErrorCode result = ib.buildImage(filename()); + if (result.isOk()) { + document->setCurrentImage(ib.image()); } - - return KisImportExportFilter::StorageCreationError; + return result; } #include diff --git a/plugins/impex/exr/exr_import.h b/plugins/impex/exr/exr_import.h index f009c2b341..5841f3ff32 100644 --- a/plugins/impex/exr/exr_import.h +++ b/plugins/impex/exr/exr_import.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef EXR_IMPORT_H_ #define EXR_IMPORT_H_ #include #include class exrImport : public KisImportExportFilter { Q_OBJECT public: exrImport(QObject *parent, const QVariantList &); ~exrImport() override; bool supportsIO() const override { return false; } - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/exr/tests/data/incorrectFormatFile.txt b/plugins/impex/exr/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/exr/tests/data/readonlyFile.txt b/plugins/impex/exr/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/exr/tests/data/writeonlyFile.txt b/plugins/impex/exr/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/exr/tests/kis_exr_test.cpp b/plugins/impex/exr/tests/kis_exr_test.cpp index f3e2f0fb55..0a649b6db7 100644 --- a/plugins/impex/exr/tests/kis_exr_test.cpp +++ b/plugins/impex/exr/tests/kis_exr_test.cpp @@ -1,94 +1,110 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_exr_test.h" #include #include #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString ExrMimetype = "application/x-extension-exr"; void KisExrTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 5); } +void KisExrTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), ExrMimetype); +} + +void KisExrTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), ExrMimetype, true); +} + +void KisExrTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), ExrMimetype); +} + void KisExrTest::testRoundTrip() { QString inputFileName(TestUtil::fetchDataFileLazy("CandleGlass.exr")); KisDocument *doc1 = KisPart::instance()->createDocument(); doc1->setFileBatchMode(true); bool r = doc1->importDocument(QUrl::fromLocalFile(inputFileName)); QVERIFY(r); QVERIFY(doc1->errorMessage().isEmpty()); QVERIFY(doc1->image()); QTemporaryFile savedFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".exr")); savedFile.setAutoRemove(true); savedFile.open(); QString savedFileName(savedFile.fileName()); QString typeName = KisMimeDatabase::mimeTypeForFile(savedFileName, false); QByteArray mimeType(typeName.toLatin1()); r = doc1->exportDocumentSync(QUrl::fromLocalFile(savedFileName), mimeType); QVERIFY(r); QVERIFY(QFileInfo(savedFileName).exists()); { KisDocument *doc2 = KisPart::instance()->createDocument(); doc2->setFileBatchMode(true); r = doc2->importDocument(QUrl::fromLocalFile(savedFileName)); QVERIFY(r); QVERIFY(doc2->errorMessage().isEmpty()); QVERIFY(doc2->image()); doc1->image()->root()->firstChild()->paintDevice()->convertToQImage(0).save("1.png"); doc2->image()->root()->firstChild()->paintDevice()->convertToQImage(0).save("2.png"); QVERIFY(TestUtil::comparePaintDevicesClever( doc1->image()->root()->firstChild()->paintDevice(), doc2->image()->root()->firstChild()->paintDevice(), 0.01 /* meaningless alpha */)); delete doc2; } savedFile.close(); delete doc1; } KISTEST_MAIN(KisExrTest) diff --git a/plugins/impex/exr/tests/kis_exr_test.h b/plugins/impex/exr/tests/kis_exr_test.h index 4dad62dfca..7119e96947 100644 --- a/plugins/impex/exr/tests/kis_exr_test.h +++ b/plugins/impex/exr/tests/kis_exr_test.h @@ -1,32 +1,35 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_EXR_TEST_H_ #define _KIS_EXR_TEST_H_ #include class KisExrTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); void testRoundTrip(); }; #endif diff --git a/plugins/impex/gif/CMakeLists.txt b/plugins/impex/gif/CMakeLists.txt index 31ad0a488d..bb7f582b6c 100644 --- a/plugins/impex/gif/CMakeLists.txt +++ b/plugins/impex/gif/CMakeLists.txt @@ -1,26 +1,28 @@ +add_subdirectory(tests) + set(kritagifexport_SOURCES kis_gif_export.cpp qgiflibhandler.cpp ) ki18n_wrap_ui(kritagifexport_SOURCES ) add_library(kritagifexport MODULE ${kritagifexport_SOURCES}) target_link_libraries(kritagifexport kritaui kritaimpex ${GIF_LIBRARY}) install(TARGETS kritagifexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritagifimport_SOURCES kis_gif_import.cpp qgiflibhandler.cpp ) ki18n_wrap_ui(kritagifimport_SOURCES ) add_library(kritagifimport MODULE ${kritagifimport_SOURCES}) target_link_libraries(kritagifimport kritaui ${GIF_LIBRARY}) install(TARGETS kritagifimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_gif.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/gif/kis_gif_export.cpp b/plugins/impex/gif/kis_gif_export.cpp index f0b2471a64..c9332d3670 100644 --- a/plugins/impex/gif/kis_gif_export.cpp +++ b/plugins/impex/gif/kis_gif_export.cpp @@ -1,72 +1,76 @@ /* * Copyright (c) 2018 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_gif_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "qgiflibhandler.h" K_PLUGIN_FACTORY_WITH_JSON(KisGIFExportFactory, "krita_gif_export.json", registerPlugin();) KisGIFExport::KisGIFExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisGIFExport::~KisGIFExport() { } -KisImportExportFilter::ConversionStatus KisGIFExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisGIFExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); QRect rc = document->savingImage()->bounds(); QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QGIFLibHandler handler; handler.setDevice(io); bool result = handler.write(image); - return (result ? KisImportExportFilter::OK : KisImportExportFilter::InternalError); + if (!result) { + KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError); + return ImportExportCodes::InternalError; + } + return ImportExportCodes::OK; } void KisGIFExport::initializeCapabilities() { QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "GIF"); } #include "kis_gif_export.moc" diff --git a/plugins/impex/gif/kis_gif_export.h b/plugins/impex/gif/kis_gif_export.h index a0106d9ae8..b2d238ca44 100644 --- a/plugins/impex/gif/kis_gif_export.h +++ b/plugins/impex/gif/kis_gif_export.h @@ -1,38 +1,38 @@ /* * Copyright (c) 2018 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_GIF_EXPORT_H_ #define _KIS_GIF_EXPORT_H_ #include #include class KisGIFExport : public KisImportExportFilter { Q_OBJECT public: KisGIFExport(QObject *parent, const QVariantList &); ~KisGIFExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/gif/kis_gif_import.cpp b/plugins/impex/gif/kis_gif_import.cpp index 86d9a7bb21..11b8957a92 100644 --- a/plugins/impex/gif/kis_gif_import.cpp +++ b/plugins/impex/gif/kis_gif_import.cpp @@ -1,84 +1,94 @@ /* * Copyright (c) 2018 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_gif_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include "qgiflibhandler.h" K_PLUGIN_FACTORY_WITH_JSON(KisGIFImportFactory, "krita_gif_import.json", registerPlugin();) KisGIFImport::KisGIFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisGIFImport::~KisGIFImport() { } -KisImportExportFilter::ConversionStatus KisGIFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisGIFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); QImage img; bool result = false; QGIFLibHandler handler; + + handler.setDevice(io); + if (!io->isReadable()) { + return ImportExportCodes::NoAccessToRead; + } + if (handler.canRead()) { result = handler.read(&img); + } else { + // handler.canRead() checks for the flag in the file; if it can't read it, maybe the format is incorrect + return ImportExportCodes::FileFormatIncorrect; } if (result == false) { - return KisImportExportFilter::CreationError; + return ImportExportCodes::FileFormatIncorrect; } const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(document->createUndoStore(), img.width(), img.height(), colorSpace, "imported from gif"); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); layer->paintDevice()->convertFromQImage(img, 0, 0, 0); image->addNode(layer.data(), image->rootLayer().data()); document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_gif_import.moc" diff --git a/plugins/impex/gif/kis_gif_import.h b/plugins/impex/gif/kis_gif_import.h index 7b9d9f90bf..8afa60ca9d 100644 --- a/plugins/impex/gif/kis_gif_import.h +++ b/plugins/impex/gif/kis_gif_import.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2018 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_GIF_IMPORT_H_ #define _KIS_GIF_IMPORT_H_ #include #include class KisGIFImport : public KisImportExportFilter { Q_OBJECT public: KisGIFImport(QObject *parent, const QVariantList &); ~KisGIFImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/gif/tests/CMakeLists.txt b/plugins/impex/gif/tests/CMakeLists.txt new file mode 100644 index 0000000000..a58fb5b17f --- /dev/null +++ b/plugins/impex/gif/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisGifTest.cpp + TEST_NAME KisGifTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/gif/tests/KisGifTest.cpp similarity index 64% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/gif/tests/KisGifTest.cpp index 6ca66688f8..5c87288247 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/gif/tests/KisGifTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "kis_svg_test.h" +#include "KisGifTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString GifMimetype = "image/gif"; + + + +void KisGifTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), GifMimetype); +} + + +void KisGifTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), GifMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisGifTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), GifMimetype); +} + + + +KISTEST_MAIN(KisGifTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/gif/tests/KisGifTest.h similarity index 74% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/gif/tests/KisGifTest.h index 5c19b0cda6..a65c46bead 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/gif/tests/KisGifTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _KIS_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_GIF_TEST_H_ +#define _KIS_GIF_TEST_H_ #include -class KisJpegTest : public QObject +class KisGifTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_BRUSH_TEST_H_ + diff --git a/plugins/impex/gif/tests/data/incorrectFormatFile.txt b/plugins/impex/gif/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/gif/tests/data/readonlyFile.txt b/plugins/impex/gif/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/gif/tests/data/writeonlyFile.txt b/plugins/impex/gif/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heif/CMakeLists.txt b/plugins/impex/heif/CMakeLists.txt index 725e7afed3..d25f3e1704 100644 --- a/plugins/impex/heif/CMakeLists.txt +++ b/plugins/impex/heif/CMakeLists.txt @@ -1,31 +1,33 @@ +add_subdirectory(tests) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${HEIF_CFLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${HEIF_CFLAGS}") add_definitions(${HEIF_DEFINITIONS}) set(kritaheifimport_SOURCES HeifImport.cpp HeifError.cpp ) add_library(kritaheifimport MODULE ${kritaheifimport_SOURCES}) target_link_libraries(kritaheifimport kritaui kritalibkra ${HEIF_LDFLAGS} ${HEIF_LIBRARIES} ) install(TARGETS kritaheifimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritaheifexport_SOURCES HeifExport.cpp HeifError.cpp ) ki18n_wrap_ui(kritaheifexport_SOURCES WdgHeifExport.ui ) add_library(kritaheifexport MODULE ${kritaheifexport_SOURCES}) target_link_libraries(kritaheifexport kritaui kritalibkra kritaimpex ${HEIF_LDFLAGS} ${HEIF_LIBRARIES} ) install(TARGETS kritaheifexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_heif.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/heif/HeifError.cpp b/plugins/impex/heif/HeifError.cpp index 41a324a1ce..e37c655936 100644 --- a/plugins/impex/heif/HeifError.cpp +++ b/plugins/impex/heif/HeifError.cpp @@ -1,64 +1,62 @@ /* * Copyright (c) 2018 Dirk Farin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "HeifError.h" -KisImportExportFilter::ConversionStatus setHeifError(KisDocument* document, +KisImportExportErrorCode setHeifError(KisDocument* document, heif::Error error) { switch (error.get_code()) { case heif_error_Ok: - return KisImportExportFilter::OK; + return ImportExportCodes::OK; case heif_error_Input_does_not_exist: // this should never happen because we do not read from file names - document->setErrorMessage(i18n("Internal error.")); - return KisImportExportFilter::InternalError; + KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError); + return ImportExportCodes::InternalError; case heif_error_Invalid_input: case heif_error_Decoder_plugin_error: - document->setErrorMessage(i18n("The HEIF file is corrupted.")); - return KisImportExportFilter::ParsingError; + return ImportExportCodes::FileFormatIncorrect; case heif_error_Unsupported_filetype: case heif_error_Unsupported_feature: - document->setErrorMessage(i18n("Krita does not support this type of HEIF file.")); - return KisImportExportFilter::NotImplemented; + return ImportExportCodes::FormatFeaturesUnsupported; case heif_error_Usage_error: case heif_error_Encoder_plugin_error: // this should never happen if we use libheif in the correct way - document->setErrorMessage(i18n("Internal libheif API error.")); - return KisImportExportFilter::InternalError; + KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError); + return ImportExportCodes::InternalError; case heif_error_Memory_allocation_error: document->setErrorMessage(i18n("Could not allocate memory.")); - return KisImportExportFilter::StorageCreationError; + return ImportExportCodes::InsufficientMemory; case heif_error_Encoding_error: document->setErrorMessage(i18n("Could not encode or write image.")); - return KisImportExportFilter::CreationError; + return ImportExportCodes::NoAccessToWrite; default: // we only get here when we forgot to handle an error ID document->setErrorMessage(i18n("Unknown error.")); - return KisImportExportFilter::InternalError; + return ImportExportCodes::Failure; } } diff --git a/plugins/impex/heif/HeifError.h b/plugins/impex/heif/HeifError.h index 3fd38b018a..39ee3b2b7d 100644 --- a/plugins/impex/heif/HeifError.h +++ b/plugins/impex/heif/HeifError.h @@ -1,31 +1,32 @@ /* * Copyright (c) 2018 Dirk Farin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef HEIF_ERROR_H_ #define HEIF_ERROR_H_ #include #include "libheif/heif_cxx.h" +#include -KisImportExportFilter::ConversionStatus setHeifError(KisDocument* document, +KisImportExportErrorCode setHeifError(KisDocument* document, heif::Error error); #endif diff --git a/plugins/impex/heif/HeifExport.cpp b/plugins/impex/heif/HeifExport.cpp index 4ff1d388a5..ddf761e939 100644 --- a/plugins/impex/heif/HeifExport.cpp +++ b/plugins/impex/heif/HeifExport.cpp @@ -1,282 +1,282 @@ /* * Copyright (c) 2018 Dirk Farin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "HeifExport.h" #include "HeifError.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include "libheif/heif_cxx.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_heif_export.json", registerPlugin();) HeifExport::HeifExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } HeifExport::~HeifExport() { } KisPropertiesConfigurationSP HeifExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("quality", 50); cfg->setProperty("lossless", true); return cfg; } KisConfigWidget *HeifExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisWdgOptionsHeif(parent); } class Writer_QIODevice : public heif::Context::Writer { public: Writer_QIODevice(QIODevice* io) : m_io(io) { } heif_error write(const void* data, size_t size) override { qint64 n = m_io->write((const char*)data,size); if (n != (qint64)size) { QString error = m_io->errorString(); heif_error err = { heif_error_Encoding_error, heif_suberror_Cannot_write_output_data, "Could not write output data" }; return err; } struct heif_error heif_error_ok = { heif_error_Ok, heif_suberror_Unspecified, "Success" }; return heif_error_ok; } private: QIODevice* m_io; }; -KisImportExportFilter::ConversionStatus HeifExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode HeifExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KisImageSP image = document->savingImage(); const KoColorSpace *cs = image->colorSpace(); // Convert to 8 bits rgba on saving if (cs->colorModelId() != RGBAColorModelID || cs->colorDepthId() != Integer8BitsColorDepthID) { cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id()); image->convertImageColorSpace(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } int quality = configuration->getInt("quality", 50); bool lossless = configuration->getBool("lossless", false); bool has_alpha = configuration->getBool(KisImportExportFilter::ImageContainsTransparencyTag, false); // If we want to add information from the document to the metadata, // we should do that here. try { // --- use standard HEVC encoder heif::Encoder encoder(heif_compression_HEVC); encoder.set_lossy_quality(quality); encoder.set_lossless(lossless); // --- convert KisImage to HEIF image --- int width = image->width(); int height = image->height(); heif::Context ctx; heif::Image img; img.create(width,height, heif_colorspace_RGB, heif_chroma_444); img.add_plane(heif_channel_R, width,height, 8); img.add_plane(heif_channel_G, width,height, 8); img.add_plane(heif_channel_B, width,height, 8); uint8_t* ptrR {0}; uint8_t* ptrG {0}; uint8_t* ptrB {0}; uint8_t* ptrA {0}; int strideR,strideG,strideB,strideA; ptrR = img.get_plane(heif_channel_R, &strideR); ptrG = img.get_plane(heif_channel_G, &strideG); ptrB = img.get_plane(heif_channel_B, &strideB); if (has_alpha) { img.add_plane(heif_channel_Alpha, width,height, 8); ptrA = img.get_plane(heif_channel_Alpha, &strideA); } KisPaintDeviceSP pd = image->projection(); for (int y=0; ycreateHLineIteratorNG(0, y, width); for (int x=0; x::red(it->rawData()); ptrG[y*strideG+x] = KoBgrTraits::green(it->rawData()); ptrB[y*strideB+x] = KoBgrTraits::blue(it->rawData()); if (has_alpha) { ptrA[y*strideA+x] = cs->opacityU8(it->rawData()); } it->nextPixel(); } } // --- encode and write image heif::ImageHandle handle = ctx.encode_image(img, encoder); // --- add Exif / XMP metadata KisExifInfoVisitor exivInfoVisitor; exivInfoVisitor.visit(image->rootLayer().data()); QScopedPointer metaDataStore; if (exivInfoVisitor.metaDataCount() == 1) { metaDataStore.reset(new KisMetaData::Store(*exivInfoVisitor.exifInfo())); } else { metaDataStore.reset(new KisMetaData::Store()); } if (!metaDataStore->empty()) { { KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); QBuffer buffer; exifIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else? QByteArray data = buffer.data(); // Write the data to the file ctx.add_exif_metadata(handle, data.constData(), data.size()); } { KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); QBuffer buffer; xmpIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else? QByteArray data = buffer.data(); // Write the data to the file ctx.add_XMP_metadata(handle, data.constData(), data.size()); } } // --- write HEIF file Writer_QIODevice writer(io); ctx.write(writer); } catch (heif::Error err) { return setHeifError(document, err); } - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } void HeifExport::initializeCapabilities() { // This checks before saving for what the file format supports: anything that is supported needs to be mentioned here QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) /*<< QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID)*/ ; addSupportedColorModels(supportedColorModels, "HEIF"); } void KisWdgOptionsHeif::setConfiguration(const KisPropertiesConfigurationSP cfg) { chkLossless->setChecked(cfg->getBool("lossless", true)); sliderQuality->setValue(qreal(cfg->getInt("quality", 50))); m_hasAlpha = cfg->getBool(KisImportExportFilter::ImageContainsTransparencyTag, false); } KisPropertiesConfigurationSP KisWdgOptionsHeif::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("lossless", chkLossless->isChecked()); cfg->setProperty("quality", int(sliderQuality->value())); cfg->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, m_hasAlpha); return cfg; } void KisWdgOptionsHeif::toggleQualitySlider(bool toggle) { // Disable the quality slider if lossless is true lossySettings->setEnabled(!toggle); } #include diff --git a/plugins/impex/heif/HeifExport.h b/plugins/impex/heif/HeifExport.h index 3eb7b3601e..3f15e4126d 100644 --- a/plugins/impex/heif/HeifExport.h +++ b/plugins/impex/heif/HeifExport.h @@ -1,72 +1,72 @@ /* * Copyright (c) 2018 Dirk Farin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef HEIF_EXPORT_H_ #define HEIF_EXPORT_H_ #include #include #include #include "ui_WdgHeifExport.h" class KisWdgOptionsHeif : public KisConfigWidget, public Ui::WdgHeifExport { Q_OBJECT public: KisWdgOptionsHeif(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); connect(chkLossless, SIGNAL(toggled(bool)), SLOT(toggleQualitySlider(bool))); sliderQuality->setRange(0, 100, 0); } void setConfiguration(const KisPropertiesConfigurationSP cfg) override; KisPropertiesConfigurationSP configuration() const override; private Q_SLOTS: void toggleQualitySlider(bool toggle); private: bool m_hasAlpha {false}; }; class HeifExport : public KisImportExportFilter { Q_OBJECT public: HeifExport(QObject *parent, const QVariantList &); ~HeifExport() override; // This should return true if the library can work with a QIODevice, and doesn't want to open the file by itself bool supportsIO() const override { return true; } - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/heif/HeifImport.cpp b/plugins/impex/heif/HeifImport.cpp index ec9675feba..e903f23483 100644 --- a/plugins/impex/heif/HeifImport.cpp +++ b/plugins/impex/heif/HeifImport.cpp @@ -1,189 +1,189 @@ /* * Copyright (c) 2018 Dirk Farin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "HeifImport.h" #include "HeifError.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include "libheif/heif_cxx.h" K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_heif_import.json", registerPlugin();) HeifImport::HeifImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } HeifImport::~HeifImport() { } class Reader_QIODevice : public heif::Context::Reader { public: Reader_QIODevice(QIODevice* device) : m_device(device) { m_total_length=m_device->bytesAvailable(); } int64_t get_position() const { return m_device->pos(); } int read(void* data, size_t size) { return m_device->read((char*)data,size) != size; } int seek(int64_t position) { return !m_device->seek(position); } heif_reader_grow_status wait_for_file_size(int64_t target_size) { return (target_size > m_total_length) ? heif_reader_grow_status_size_beyond_eof : heif_reader_grow_status_size_reached; } private: QIODevice* m_device; int64_t m_total_length; }; -KisImportExportFilter::ConversionStatus HeifImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode HeifImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { // Wrap input stream into heif Reader object Reader_QIODevice reader(io); try { heif::Context ctx; ctx.read_from_reader(reader); // decode primary image heif::ImageHandle handle = ctx.get_primary_image_handle(); heif::Image heifimage = handle.decode_image(heif_colorspace_RGB, heif_chroma_444); int width =handle.get_width(); int height=handle.get_height(); bool hasAlpha = handle.has_alpha_channel(); // convert HEIF image to Krita KisDocument int strideR, strideG, strideB, strideA; const uint8_t* imgR = heifimage.get_plane(heif_channel_R, &strideR); const uint8_t* imgG = heifimage.get_plane(heif_channel_G, &strideG); const uint8_t* imgB = heifimage.get_plane(heif_channel_B, &strideB); const uint8_t* imgA = heifimage.get_plane(heif_channel_Alpha, &strideA); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(document->createUndoStore(), width, height, colorSpace, "HEIF image"); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); for (int y=0;ypaintDevice()->createHLineIteratorNG(0, y, width); for (int x=0;x::setRed(it->rawData(), imgR[y*strideR+x]); KoBgrTraits::setGreen(it->rawData(), imgG[y*strideG+x]); KoBgrTraits::setBlue(it->rawData(), imgB[y*strideB+x]); if (hasAlpha) { colorSpace->setOpacity(it->rawData(), quint8(imgA[y*strideA+x]), 1); } else { colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } it->nextPixel(); } } image->addNode(layer.data(), image->rootLayer().data()); // --- Iterate through all metadata blocks and extract Exif and XMP metadata --- std::vector metadata_IDs = handle.get_list_of_metadata_block_IDs(); for (heif_item_id id : metadata_IDs) { if (handle.get_metadata_type(id) == "Exif") { // Read exif information std::vector exif_data = handle.get_metadata(id); if (exif_data.size()>4) { uint32_t skip = ((exif_data[0]<<24) | (exif_data[1]<<16) | (exif_data[2]<<8) | exif_data[3]) + 4; if (exif_data.size()>skip) { KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); // Copy the exif data into the byte array QByteArray ba; ba.append((char*)(exif_data.data()+skip), exif_data.size()-skip); QBuffer buf(&ba); exifIO->loadFrom(layer->metaData(), &buf); } } } if (handle.get_metadata_type(id) == "mime" && handle.get_metadata_content_type(id) == "application/rdf+xml") { // Read XMP information std::vector xmp_data = handle.get_metadata(id); KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); // Copy the xmp data into the byte array QByteArray ba; ba.append((char*)(xmp_data.data()), xmp_data.size()); QBuffer buf(&ba); xmpIO->loadFrom(layer->metaData(), &buf); } } document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } catch (heif::Error err) { return setHeifError(document, err); } } #include diff --git a/plugins/impex/heif/HeifImport.h b/plugins/impex/heif/HeifImport.h index 6f162eeba6..9c99161af5 100644 --- a/plugins/impex/heif/HeifImport.h +++ b/plugins/impex/heif/HeifImport.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2018 Dirk Farin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef HEIF_IMPORT_H_ #define HEIF_IMPORT_H_ #include #include class HeifImport : public KisImportExportFilter { Q_OBJECT public: HeifImport(QObject *parent, const QVariantList &); ~HeifImport() override; bool supportsIO() const override { return true; } - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/heif/tests/CMakeLists.txt b/plugins/impex/heif/tests/CMakeLists.txt new file mode 100644 index 0000000000..0f9cfe6c94 --- /dev/null +++ b/plugins/impex/heif/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisHeifTest.cpp + TEST_NAME KisHeifTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/heif/tests/KisHeifTest.cpp similarity index 64% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/heif/tests/KisHeifTest.cpp index 6ca66688f8..7b95b42ec7 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/heif/tests/KisHeifTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "kis_svg_test.h" +#include "KisHeifTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString HeifMimetype = "image/heic"; + + + +void KisHeifTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), HeifMimetype); +} + + +void KisHeifTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), HeifMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisHeifTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), HeifMimetype); +} + + + +KISTEST_MAIN(KisHeifTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/heif/tests/KisHeifTest.h similarity index 73% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/heif/tests/KisHeifTest.h index 5c19b0cda6..ea09c9a836 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/heif/tests/KisHeifTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _KIS_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_HEIF_TEST_H_ +#define _KIS_HEIF_TEST_H_ #include -class KisJpegTest : public QObject +class KisHeifTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_HEIF_TEST_H_ + diff --git a/plugins/impex/heif/tests/data/incorrectFormatFile.txt b/plugins/impex/heif/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heif/tests/data/readonlyFile.txt b/plugins/impex/heif/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heif/tests/data/writeonlyFile.txt b/plugins/impex/heif/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heightmap/kis_heightmap_export.cpp b/plugins/impex/heightmap/kis_heightmap_export.cpp index 085dc7e4f3..165b10b2a4 100644 --- a/plugins/impex/heightmap/kis_heightmap_export.cpp +++ b/plugins/impex/heightmap/kis_heightmap_export.cpp @@ -1,148 +1,148 @@ /* * Copyright (c) 2014 Boudewijn Rempt * Copyright (c) 2017 Victor Wåhlström * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_heightmap_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_options_heightmap.h" #include "kis_heightmap_utils.h" K_PLUGIN_FACTORY_WITH_JSON(KisHeightMapExportFactory, "krita_heightmap_export.json", registerPlugin();) template static void writeData(KisPaintDeviceSP pd, const QRect &bounds, QDataStream &out_stream) { KIS_ASSERT_RECOVER_RETURN(pd); KisSequentialConstIterator it(pd, bounds); while (it.nextPixel()) { out_stream << KoGrayTraits::gray(const_cast(it.rawDataConst())); } } KisHeightMapExport::KisHeightMapExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisHeightMapExport::~KisHeightMapExport() { } KisPropertiesConfigurationSP KisHeightMapExport::defaultConfiguration(const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("endianness", 0); return cfg; } KisConfigWidget *KisHeightMapExport::createConfigurationWidget(QWidget *parent, const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); bool export_mode = true; KisWdgOptionsHeightmap* wdg = new KisWdgOptionsHeightmap(parent, export_mode); return wdg; } void KisHeightMapExport::initializeCapabilities() { if (mimeType() == "image/x-r8") { QList > supportedColorModels; supportedColorModels << QPair() << QPair(GrayAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "R8 Heightmap"); } else if (mimeType() == "image/x-r16") { QList > supportedColorModels; supportedColorModels << QPair() << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "R16 Heightmap"); } else if (mimeType() == "image/x-r32") { QList > supportedColorModels; supportedColorModels << QPair() << QPair(GrayAColorModelID, Float32BitsColorDepthID); addSupportedColorModels(supportedColorModels, "R32 Heightmap"); } } -KisImportExportFilter::ConversionStatus KisHeightMapExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisHeightMapExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { - KIS_ASSERT_RECOVER_RETURN_VALUE(mimeType() == "image/x-r16" || mimeType() == "image/x-r8" || mimeType() == "image/x-r32", KisImportExportFilter::WrongFormat); + KIS_ASSERT_RECOVER_RETURN_VALUE(mimeType() == "image/x-r16" || mimeType() == "image/x-r8" || mimeType() == "image/x-r32", ImportExportCodes::FileFormatIncorrect); KisImageSP image = document->savingImage(); QDataStream::ByteOrder bo = configuration->getInt("endianness", 1) == 0 ? QDataStream::BigEndian : QDataStream::LittleEndian; KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); QDataStream s(io); s.setByteOrder(bo); // needed for 32bit float data s.setFloatingPointPrecision(QDataStream::SinglePrecision); KoID target_co_model = GrayAColorModelID; KoID target_co_depth = KisHeightmapUtils::mimeTypeToKoID(mimeType()); KIS_ASSERT(!target_co_depth.id().isNull()); if (pd->colorSpace()->colorModelId() != target_co_model || pd->colorSpace()->colorDepthId() != target_co_depth) { pd = new KisPaintDevice(*pd.data()); pd->convertTo(KoColorSpaceRegistry::instance()->colorSpace(target_co_model.id(), target_co_depth.id())); } if (target_co_depth == Float32BitsColorDepthID) { writeData(pd, image->bounds(), s); } else if (target_co_depth == Integer16BitsColorDepthID) { writeData(pd, image->bounds(), s); } else if (target_co_depth == Integer8BitsColorDepthID) { writeData(pd, image->bounds(), s); } else { - return KisImportExportFilter::InternalError; + KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError); } - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_heightmap_export.moc" diff --git a/plugins/impex/heightmap/kis_heightmap_export.h b/plugins/impex/heightmap/kis_heightmap_export.h index f686961e27..cf966192c9 100644 --- a/plugins/impex/heightmap/kis_heightmap_export.h +++ b/plugins/impex/heightmap/kis_heightmap_export.h @@ -1,41 +1,41 @@ /* * Copyright (c) 2014 Boudewijn Rempt * Copyright (c) 2017 Victor Wåhlström * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_HeightMap_EXPORT_H_ #define _KIS_HeightMap_EXPORT_H_ #include #include #include class KisHeightMapExport : public KisImportExportFilter { Q_OBJECT public: KisHeightMapExport(QObject *parent, const QVariantList &); ~KisHeightMapExport() override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/heightmap/kis_heightmap_import.cpp b/plugins/impex/heightmap/kis_heightmap_import.cpp index 9f899267b5..6e53044f7f 100644 --- a/plugins/impex/heightmap/kis_heightmap_import.cpp +++ b/plugins/impex/heightmap/kis_heightmap_import.cpp @@ -1,198 +1,204 @@ /* * Copyright (c) 2014 Boudewijn Rempt * Copyright (c) 2017 Victor Wåhlström * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_heightmap_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_options_heightmap.h" #include "kis_heightmap_utils.h" K_PLUGIN_FACTORY_WITH_JSON(HeightMapImportFactory, "krita_heightmap_import.json", registerPlugin();) template void fillData(KisPaintDeviceSP pd, int w, int h, QDataStream &stream) { KIS_ASSERT_RECOVER_RETURN(pd); T pixel; for (int i = 0; i < h; ++i) { KisHLineIteratorSP it = pd->createHLineIteratorNG(0, i, w); do { stream >> pixel; KoGrayTraits::setGray(it->rawData(), pixel); KoGrayTraits::setOpacity(it->rawData(), OPACITY_OPAQUE_F, 1); } while(it->nextPixel()); } } KisHeightMapImport::KisHeightMapImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisHeightMapImport::~KisHeightMapImport() { } -KisImportExportFilter::ConversionStatus KisHeightMapImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisHeightMapImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); KoID depthId = KisHeightmapUtils::mimeTypeToKoID(mimeType()); if (depthId.id().isNull()) { document->setErrorMessage(i18n("Unknown file type")); - return KisImportExportFilter::WrongFormat; + return ImportExportCodes::FileFormatIncorrect; } int w = 0; int h = 0; KIS_ASSERT(io->isOpen()); const quint64 size = io->size(); + if (size == 0) { + return ImportExportCodes::FileFormatIncorrect; + } + QDataStream::ByteOrder bo = QDataStream::LittleEndian; if (!batchMode()) { QApplication::restoreOverrideCursor(); KoDialog* kdb = new KoDialog(0); kdb->setWindowTitle(i18n("Heightmap Import Options")); kdb->setButtons(KoDialog::Ok | KoDialog::Cancel); KisWdgOptionsHeightmap* wdg = new KisWdgOptionsHeightmap(kdb); kdb->setMainWidget(wdg); connect(wdg, SIGNAL(statusUpdated(bool)), kdb, SLOT(enableButtonOk(bool))); KisConfig config(true); QString filterConfig = config.importConfiguration(mimeType()); KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration); cfg->fromXML(filterConfig); int endianness = cfg->getInt("endianness", 1); if (endianness == 0) { wdg->radioBig->setChecked(true); } else { wdg->radioLittle->setChecked(true); } wdg->fileSizeLabel->setText(QString::number(size)); if(depthId == Integer8BitsColorDepthID) { wdg->bppLabel->setText(QString::number(8)); wdg->typeLabel->setText("Integer"); } else if(depthId == Integer16BitsColorDepthID) { wdg->bppLabel->setText(QString::number(16)); wdg->typeLabel->setText("Integer"); } else if(depthId == Float32BitsColorDepthID) { wdg->bppLabel->setText(QString::number(32)); wdg->typeLabel->setText("Float"); } else { - return KisImportExportFilter::InternalError; + KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError); + return ImportExportCodes::InternalError; } if (kdb->exec() == QDialog::Rejected) { - return KisImportExportFilter::UserCancelled; + return ImportExportCodes::Cancelled; } cfg->setProperty("endianness", wdg->radioBig->isChecked() ? 0 : 1); config.setImportConfiguration(mimeType(), cfg); w = wdg->widthInput->value(); h = wdg->heightInput->value(); bo = QDataStream::LittleEndian; cfg->setProperty("endianness", 1); if (wdg->radioBig->isChecked()) { bo = QDataStream::BigEndian; cfg->setProperty("endianness", 0); } KisConfig(true).setExportConfiguration(mimeType(), cfg); } else { const int pixelSize = depthId == Float32BitsColorDepthID ? 4 : depthId == Integer16BitsColorDepthID ? 2 : 1; const int numPixels = size / pixelSize; w = std::sqrt(numPixels); h = numPixels / w; bo = QDataStream::LittleEndian; } QDataStream s(io); s.setByteOrder(bo); // needed for 32bit float data s.setFloatingPointPrecision(QDataStream::SinglePrecision); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), depthId.id(), "Gray-D50-elle-V2-srgbtrc.icc"); KisImageSP image = new KisImage(document->createUndoStore(), w, h, colorSpace, "imported heightmap"); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); if (depthId == Float32BitsColorDepthID) { fillData(layer->paintDevice(), w, h, s); } else if (depthId == Integer16BitsColorDepthID) { fillData(layer->paintDevice(), w, h, s); } else if (depthId == Integer8BitsColorDepthID) { fillData(layer->paintDevice(), w, h, s); } else { - return KisImportExportFilter::InternalError; + KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError); + return ImportExportCodes::InternalError; } image->addNode(layer.data(), image->rootLayer().data()); document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_heightmap_import.moc" diff --git a/plugins/impex/heightmap/kis_heightmap_import.h b/plugins/impex/heightmap/kis_heightmap_import.h index 94f1df9449..1a89a8171d 100644 --- a/plugins/impex/heightmap/kis_heightmap_import.h +++ b/plugins/impex/heightmap/kis_heightmap_import.h @@ -1,38 +1,38 @@ /* * Copyright (c) 2014 Boudewijn Rempt * Copyright (c) 2017 Victor Wåhlström * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_HeightMap_IMPORT_H_ #define _KIS_HeightMap_IMPORT_H_ #include #include class KisHeightMapImport : public KisImportExportFilter { Q_OBJECT public: KisHeightMapImport(QObject *parent, const QVariantList &); ~KisHeightMapImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/heightmap/tests/data/incorrectFormatFile.txt b/plugins/impex/heightmap/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heightmap/tests/data/readonlyFile.txt b/plugins/impex/heightmap/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heightmap/tests/data/writeonlyFile.txt b/plugins/impex/heightmap/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heightmap/tests/kis_heightmap_test.cpp b/plugins/impex/heightmap/tests/kis_heightmap_test.cpp index 13fc7f3026..82c5d7d3ad 100644 --- a/plugins/impex/heightmap/tests/kis_heightmap_test.cpp +++ b/plugins/impex/heightmap/tests/kis_heightmap_test.cpp @@ -1,38 +1,62 @@ /* * Copyright (c) 2017 Victor Wåhlström * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_heightmap_test.h" #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString HeightmapMimetype = "image/x-r8"; + + void KisHeightmapTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); } + + + +void KisHeightmapTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), HeightmapMimetype); +} + + +void KisHeightmapTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), HeightmapMimetype); +} + + +void KisHeightmapTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), HeightmapMimetype); +} + + KISTEST_MAIN(KisHeightmapTest) diff --git a/plugins/impex/heightmap/tests/kis_heightmap_test.h b/plugins/impex/heightmap/tests/kis_heightmap_test.h index b22fa1d2ba..05d960740e 100644 --- a/plugins/impex/heightmap/tests/kis_heightmap_test.h +++ b/plugins/impex/heightmap/tests/kis_heightmap_test.h @@ -1,31 +1,35 @@ /* * Copyright (c) 2017 Victor Wåhlström * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_HEIGHTMAP_TEST_H_ #define _KIS_HEIGHTMAP_TEST_H_ #include class KisHeightmapTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; #endif // _KIS_HEIGHTMAP_TEST_H_ diff --git a/plugins/impex/jpeg/kis_jpeg_converter.cc b/plugins/impex/jpeg/kis_jpeg_converter.cc index eca195fde1..276b8af756 100644 --- a/plugins/impex/jpeg/kis_jpeg_converter.cc +++ b/plugins/impex/jpeg/kis_jpeg_converter.cc @@ -1,730 +1,728 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_jpeg_converter.h" #include #include #include #ifdef HAVE_LCMS2 # include #else # include #endif extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorModelStandardIds.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ #define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ #define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ #define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) const char photoshopMarker[] = "Photoshop 3.0\0"; //const char photoshopBimId_[] = "8BIM"; const uint16_t photoshopIptc = 0x0404; const char xmpMarker[] = "http://ns.adobe.com/xap/1.0/\0"; const QByteArray photoshopIptc_((char*)&photoshopIptc, 2); namespace { void jpegErrorExit ( j_common_ptr cinfo ) { char jpegLastErrorMsg[JMSG_LENGTH_MAX]; /* Create the message */ ( *( cinfo->err->format_message ) ) ( cinfo, jpegLastErrorMsg ); /* Jump to the setjmp point */ throw std::runtime_error( jpegLastErrorMsg ); // or your preferred exception ... } J_COLOR_SPACE getColorTypeforColorSpace(const KoColorSpace * cs) { if (KoID(cs->id()) == KoID("GRAYA") || cs->id() == "GRAYAU16" || cs->id() == "GRAYA16") { return JCS_GRAYSCALE; } if (KoID(cs->id()) == KoID("RGBA") || KoID(cs->id()) == KoID("RGBA16")) { return JCS_RGB; } if (KoID(cs->id()) == KoID("CMYK") || KoID(cs->id()) == KoID("CMYKAU16")) { return JCS_CMYK; } return JCS_UNKNOWN; } QString getColorSpaceModelForColorType(J_COLOR_SPACE color_type) { dbgFile << "color_type =" << color_type; if (color_type == JCS_GRAYSCALE) { return GrayAColorModelID.id(); } else if (color_type == JCS_RGB) { return RGBAColorModelID.id(); } else if (color_type == JCS_CMYK) { return CMYKAColorModelID.id(); } return ""; } } struct KisJPEGConverter::Private { Private(KisDocument *doc, bool batchMode) : doc(doc), stop(false), batchMode(batchMode) {} KisImageSP image; KisDocument *doc; bool stop; bool batchMode; }; KisJPEGConverter::KisJPEGConverter(KisDocument *doc, bool batchMode) : m_d(new Private(doc, batchMode)) { } KisJPEGConverter::~KisJPEGConverter() { } -KisImageBuilder_Result KisJPEGConverter::decode(QIODevice *io) +KisImportExportErrorCode KisJPEGConverter::decode(QIODevice *io) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = jpegErrorExit; try { jpeg_create_decompress(&cinfo); KisJPEGSource::setSource(&cinfo, io); jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF); /* Save APP0..APP15 markers */ for (int m = 0; m < 16; m++) jpeg_save_markers(&cinfo, JPEG_APP0 + m, 0xFFFF); // setup_read_icc_profile(&cinfo); // read header jpeg_read_header(&cinfo, (boolean)true); // start reading jpeg_start_decompress(&cinfo); // Get the colorspace QString modelId = getColorSpaceModelForColorType(cinfo.out_color_space); if (modelId.isEmpty()) { dbgFile << "unsupported colorspace :" << cinfo.out_color_space; jpeg_destroy_decompress(&cinfo); - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } uchar* profile_data; uint profile_len; const KoColorProfile* profile = 0; QByteArray profile_rawdata; if (read_icc_profile(&cinfo, &profile_data, &profile_len)) { profile_rawdata.resize(profile_len); memcpy(profile_rawdata.data(), profile_data, profile_len); cmsHPROFILE hProfile = cmsOpenProfileFromMem(profile_data, profile_len); if (hProfile != (cmsHPROFILE) 0) { profile = KoColorSpaceRegistry::instance()->createColorProfile(modelId, Integer8BitsColorDepthID.id(), profile_rawdata); Q_CHECK_PTR(profile); dbgFile <<"profile name:" << profile->name() <<" product information:" << profile->info(); if (!profile->isSuitableForOutput()) { dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user } } } const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(modelId, Integer8BitsColorDepthID.id()); // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << modelId; profile = 0; } // Retrieve a pointer to the colorspace const KoColorSpace* cs; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile:" << profile -> name() << ""; cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile); } else cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), ""); if (cs == 0) { dbgFile << "unknown colorspace"; jpeg_destroy_decompress(&cinfo); - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } // TODO fixit // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { transform = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Apparently an invalid transform was created from the profile. See bug https://bugs.kde.org/show_bug.cgi?id=255451. // After 2.3: warn the user! if (transform && !transform->isValid()) { delete transform; transform = 0; } // Creating the KisImageSP if (!m_d->image) { m_d->image = new KisImage(m_d->doc->createUndoStore(), cinfo.image_width, cinfo.image_height, cs, "built image"); Q_CHECK_PTR(m_d->image); } // Set resolution double xres = 72, yres = 72; if (cinfo.density_unit == 1) { xres = cinfo.X_density; yres = cinfo.Y_density; } else if (cinfo.density_unit == 2) { xres = cinfo.X_density * 2.54; yres = cinfo.Y_density * 2.54; } if (xres < 72) { xres = 72; } if (yres < 72) { yres = 72; } m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points // Create layer KisPaintLayerSP layer = KisPaintLayerSP(new KisPaintLayer(m_d->image.data(), m_d->image -> nextLayerName(), quint8_MAX)); // Read data JSAMPROW row_pointer = new JSAMPLE[cinfo.image_width*cinfo.num_components]; for (; cinfo.output_scanline < cinfo.image_height;) { KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, cinfo.output_scanline, cinfo.image_width); jpeg_read_scanlines(&cinfo, &row_pointer, 1); quint8 *src = row_pointer; switch (cinfo.out_color_space) { case JCS_GRAYSCALE: do { quint8 *d = it->rawData(); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[1] = quint8_MAX; } while (it->nextPixel()); break; case JCS_RGB: do { quint8 *d = it->rawData(); d[2] = *(src++); d[1] = *(src++); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[3] = quint8_MAX; } while (it->nextPixel()); break; case JCS_CMYK: do { quint8 *d = it->rawData(); d[0] = quint8_MAX - *(src++); d[1] = quint8_MAX - *(src++); d[2] = quint8_MAX - *(src++); d[3] = quint8_MAX - *(src++); if (transform) transform->transform(d, d, 1); d[4] = quint8_MAX; } while (it->nextPixel()); break; default: - return KisImageBuilder_RESULT_UNSUPPORTED; + return ImportExportCodes::FormatFeaturesUnsupported; } } m_d->image->addNode(KisNodeSP(layer.data()), m_d->image->rootLayer().data()); // Read exif information dbgFile << "Looking for exif information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 14) { continue; /* Exif data is in an APP1 marker of at least 14 octets */ } if (GETJOCTET(marker->data[0]) != (JOCTET) 0x45 || GETJOCTET(marker->data[1]) != (JOCTET) 0x78 || GETJOCTET(marker->data[2]) != (JOCTET) 0x69 || GETJOCTET(marker->data[3]) != (JOCTET) 0x66 || GETJOCTET(marker->data[4]) != (JOCTET) 0x00 || GETJOCTET(marker->data[5]) != (JOCTET) 0x00) continue; /* no Exif header */ dbgFile << "Found exif information of length :" << marker->data_length; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QByteArray byteArray((const char*)marker->data + 6, marker->data_length - 6); QBuffer buf(&byteArray); exifIO->loadFrom(layer->metaData(), &buf); // Interpret orientation tag if (layer->metaData()->containsEntry("http://ns.adobe.com/tiff/1.0/", "Orientation")) { KisMetaData::Entry& entry = layer->metaData()->getEntry("http://ns.adobe.com/tiff/1.0/", "Orientation"); if (entry.value().type() == KisMetaData::Value::Variant) { switch (entry.value().asVariant().toInt()) { case 2: KisTransformWorker::mirrorY(layer->paintDevice()); break; case 3: image()->rotateImage(M_PI); break; case 4: KisTransformWorker::mirrorX(layer->paintDevice()); break; case 5: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorY(layer->paintDevice()); break; case 6: image()->rotateImage(M_PI / 2); break; case 7: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorX(layer->paintDevice()); break; case 8: image()->rotateImage(-M_PI / 2 + M_PI*2); break; default: break; } } entry.value().setVariant(1); } break; } dbgFile << "Looking for IPTC information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 13) || marker->data_length < 14) { continue; /* IPTC data is in an APP13 marker of at least 16 octets */ } if (memcmp(marker->data, photoshopMarker, 14) != 0) { for (int i = 0; i < 14; i++) { dbgFile << (int)(*(marker->data + i)) << "" << (int)(photoshopMarker[i]); } dbgFile << "No photoshop marker"; continue; /* No IPTC Header */ } dbgFile << "Found Photoshop information of length :" << marker->data_length; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); const Exiv2::byte *record = 0; uint32_t sizeIptc = 0; uint32_t sizeHdr = 0; // Find actual Iptc data within the APP13 segment if (!Exiv2::Photoshop::locateIptcIrb((Exiv2::byte*)(marker->data + 14), marker->data_length - 14, &record, &sizeHdr, &sizeIptc)) { if (sizeIptc) { // Decode the IPTC data QByteArray byteArray((const char*)(record + sizeHdr), sizeIptc); QBuffer buf(&byteArray); iptcIO->loadFrom(layer->metaData(), &buf); } else { dbgFile << "IPTC Not found in Photoshop marker"; } } break; } dbgFile << "Looking for XMP information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 31) { continue; /* XMP data is in an APP1 marker of at least 31 octets */ } if (memcmp(marker->data, xmpMarker, 29) != 0) { dbgFile << "Not XMP marker"; continue; /* No xmp Header */ } dbgFile << "Found XMP Marker of length " << marker->data_length; QByteArray byteArray((const char*)marker->data + 29, marker->data_length - 29); KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); xmpIO->loadFrom(layer->metaData(), new QBuffer(&byteArray)); break; } // Dump loaded metadata layer->metaData()->debugDump(); // Check whether the metadata has resolution info, too... if (cinfo.density_unit == 0 && layer->metaData()->containsEntry("tiff:XResolution") && layer->metaData()->containsEntry("tiff:YResolution")) { double xres = layer->metaData()->getEntry("tiff:XResolution").value().asDouble(); double yres = layer->metaData()->getEntry("tiff:YResolution").value().asDouble(); if (xres != 0 && yres != 0) { m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points } } // Finish decompression jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); delete [] row_pointer; - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } catch( std::runtime_error &e) { jpeg_destroy_decompress(&cinfo); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } } -KisImageBuilder_Result KisJPEGConverter::buildImage(QIODevice *io) +KisImportExportErrorCode KisJPEGConverter::buildImage(QIODevice *io) { return decode(io); } KisImageSP KisJPEGConverter::image() { return m_d->image; } -KisImageBuilder_Result KisJPEGConverter::buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData) +KisImportExportErrorCode KisJPEGConverter::buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData) { - if (!layer) - return KisImageBuilder_RESULT_INVALID_ARG; + KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError); KisImageSP image = KisImageSP(layer->image()); - if (!image) - return KisImageBuilder_RESULT_EMPTY; + KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError); const KoColorSpace * cs = layer->colorSpace(); J_COLOR_SPACE color_type = getColorTypeforColorSpace(cs); if (color_type == JCS_UNKNOWN) { layer->paintDevice()->convertTo(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); cs = KoColorSpaceRegistry::instance()->rgb8(); color_type = JCS_RGB; } if (options.forceSRGB) { const KoColorSpace* dst = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), layer->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)"); layer->paintDevice()->convertTo(dst); cs = dst; color_type = JCS_RGB; } uint height = image->height(); uint width = image->width(); // Initialize structure struct jpeg_compress_struct cinfo; // Initialize error output struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); // Initialize output stream KisJPEGDestination::setDestination(&cinfo, io); cinfo.image_width = width; // image width and height, in pixels cinfo.image_height = height; cinfo.input_components = cs->colorChannelCount(); // number of color channels per pixel */ cinfo.in_color_space = color_type; // colorspace of input image // Set default compression parameters jpeg_set_defaults(&cinfo); // Customize them jpeg_set_quality(&cinfo, options.quality, (boolean)options.baseLineJPEG); if (options.progressive) { jpeg_simple_progression(&cinfo); } // Optimize ? cinfo.optimize_coding = (boolean)options.optimize; // Smoothing cinfo.smoothing_factor = (boolean)options.smooth; // Subsampling switch (options.subsampling) { default: case 0: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 1: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 2: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 3: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; } // Save resolution cinfo.X_density = INCH_TO_POINT(image->xRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.Y_density = INCH_TO_POINT(image->yRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.density_unit = 1; cinfo.write_JFIF_header = (boolean)true; // Start compression jpeg_start_compress(&cinfo, (boolean)true); // Save exif and iptc information if any available if (metaData && !metaData->empty()) { metaData->applyFilters(options.filters); // Save EXIF if (options.exif) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QBuffer buffer; exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "Exif information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 1, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "EXIF information could not be saved."; // TODO: warn the user ? } } // Save IPTC if (options.iptc) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); QBuffer buffer; iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "IPTC information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 13, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "IPTC information could not be saved."; // TODO: warn the user ? } } // Save XMP if (options.xmp) { dbgFile << "Trying to save XMP information"; KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); QBuffer buffer; xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "XMP information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 14, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "XMP information could not be saved."; // TODO: warn the user ? } } } KisPaintDeviceSP dev = new KisPaintDevice(layer->colorSpace()); KoColor c(options.transparencyFillColor, layer->colorSpace()); dev->fill(QRect(0, 0, width, height), c); KisPainter gc(dev); gc.bitBlt(QPoint(0, 0), layer->paintDevice(), QRect(0, 0, width, height)); gc.end(); if (options.saveProfile) { const KoColorProfile* colorProfile = layer->colorSpace()->profile(); QByteArray colorProfileData = colorProfile->rawData(); write_icc_profile(& cinfo, (uchar*) colorProfileData.data(), colorProfileData.size()); } // Write data information JSAMPROW row_pointer = new JSAMPLE[width*cinfo.input_components]; int color_nb_bits = 8 * layer->paintDevice()->pixelSize() / layer->paintDevice()->channelCount(); for (; cinfo.next_scanline < height;) { KisHLineConstIteratorSP it = dev->createHLineConstIteratorNG(0, cinfo.next_scanline, width); quint8 *dst = row_pointer; switch (color_type) { case JCS_GRAYSCALE: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 0);//d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_RGB: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 2); //d[2] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 1); //d[1] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 0); //d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_CMYK: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - cs->scaleToU8(d, 0);//quint8_MAX - d[0] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 1);//quint8_MAX - d[1] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 2);//quint8_MAX - d[2] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 3);//quint8_MAX - d[3] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - d[0]; *(dst++) = quint8_MAX - d[1]; *(dst++) = quint8_MAX - d[2]; *(dst++) = quint8_MAX - d[3]; } while (it->nextPixel()); } break; default: delete [] row_pointer; jpeg_destroy_compress(&cinfo); - return KisImageBuilder_RESULT_UNSUPPORTED; + return ImportExportCodes::FormatFeaturesUnsupported; } jpeg_write_scanlines(&cinfo, &row_pointer, 1); } // Writing is over jpeg_finish_compress(&cinfo); delete [] row_pointer; // Free memory jpeg_destroy_compress(&cinfo); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } void KisJPEGConverter::cancel() { m_d->stop = true; } diff --git a/plugins/impex/jpeg/kis_jpeg_converter.h b/plugins/impex/jpeg/kis_jpeg_converter.h index 45a2c9c0ac..070c013ce3 100644 --- a/plugins/impex/jpeg/kis_jpeg_converter.h +++ b/plugins/impex/jpeg/kis_jpeg_converter.h @@ -1,86 +1,86 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_JPEG_CONVERTER_H_ #define _KIS_JPEG_CONVERTER_H_ #include extern "C" { #include } #include #include #include "kis_types.h" #include "kis_annotation.h" -#include +#include class KisDocument; namespace KisMetaData { class Filter; } struct KisJPEGOptions { int quality; bool progressive; bool optimize; int smooth; bool baseLineJPEG; int subsampling; bool exif; bool iptc; bool xmp; QList filters; QColor transparencyFillColor; bool forceSRGB; bool saveProfile; bool storeDocumentMetaData; //this is for getting the metadata from the document info. bool storeAuthor; //this is for storing author data from the document info. }; namespace KisMetaData { class Store; } class KisJPEGConverter : public QObject { Q_OBJECT public: KisJPEGConverter(KisDocument *doc, bool batchMode = false); ~KisJPEGConverter() override; public: - KisImageBuilder_Result buildImage(QIODevice *io); - KisImageBuilder_Result buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData); + KisImportExportErrorCode buildImage(QIODevice *io); + KisImportExportErrorCode buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData); /** Retrieve the constructed image */ KisImageSP image(); public Q_SLOTS: virtual void cancel(); private: - KisImageBuilder_Result decode(QIODevice *io); + KisImportExportErrorCode decode(QIODevice *io); private: struct Private; QScopedPointer m_d; }; #endif diff --git a/plugins/impex/jpeg/kis_jpeg_export.cc b/plugins/impex/jpeg/kis_jpeg_export.cc index eb2a7526cd..6bd8b31c72 100644 --- a/plugins/impex/jpeg/kis_jpeg_export.cc +++ b/plugins/impex/jpeg/kis_jpeg_export.cc @@ -1,296 +1,291 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_jpeg_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_jpeg_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(KisJPEGExportFactory, "krita_jpeg_export.json", registerPlugin();) KisJPEGExport::KisJPEGExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisJPEGExport::~KisJPEGExport() { } -KisImportExportFilter::ConversionStatus KisJPEGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisJPEGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KisImageSP image = document->savingImage(); Q_CHECK_PTR(image); // An extra option to pass to the config widget to set the state correctly, this isn't saved const KoColorSpace* cs = image->projection()->colorSpace(); bool sRGB = cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive); configuration->setProperty("is_sRGB", sRGB); KisJPEGOptions options; options.progressive = configuration->getBool("progressive", false); options.quality = configuration->getInt("quality", 80); options.forceSRGB = configuration->getBool("forceSRGB", false); options.saveProfile = configuration->getBool("saveProfile", true); options.optimize = configuration->getBool("optimize", true); options.smooth = configuration->getInt("smoothing", 0); options.baseLineJPEG = configuration->getBool("baseline", true); options.subsampling = configuration->getInt("subsampling", 0); options.exif = configuration->getBool("exif", true); options.iptc = configuration->getBool("iptc", true); options.xmp = configuration->getBool("xmp", true); KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromQColor(Qt::white); options.transparencyFillColor = configuration->getColor("transparencyFillcolor", c).toQColor(); KisMetaData::FilterRegistryModel m; m.setEnabledFilters(configuration->getString("filters").split(",")); options.filters = m.enabledFilters(); options.storeAuthor = configuration->getBool("storeAuthor", false); options.storeDocumentMetaData = configuration->getBool("storeMetaData", false); KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); KisJPEGConverter kpc(document, batchMode()); KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd); KisExifInfoVisitor exivInfoVisitor; exivInfoVisitor.visit(image->rootLayer().data()); QScopedPointer metaDataStore; if (exivInfoVisitor.metaDataCount() == 1) { metaDataStore.reset(new KisMetaData::Store(*exivInfoVisitor.exifInfo())); } else { metaDataStore.reset(new KisMetaData::Store()); } //add extra meta-data here const KisMetaData::Schema* dcSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri); Q_ASSERT(dcSchema); if (options.storeDocumentMetaData) { QString title = document->documentInfo()->aboutInfo("title"); if (!title.isEmpty()) { if (metaDataStore->containsEntry("title")) { metaDataStore->removeEntry("title"); } metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "title", KisMetaData::Value(QVariant(title)))); } QString description = document->documentInfo()->aboutInfo("subject"); if (description.isEmpty()) { description = document->documentInfo()->aboutInfo("abstract"); } if (!description.isEmpty()) { QString keywords = document->documentInfo()->aboutInfo("keyword"); if (!keywords.isEmpty()) { description = description + " keywords: " + keywords; } if (metaDataStore->containsEntry("description")) { metaDataStore->removeEntry("description"); } metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "description", KisMetaData::Value(QVariant(description)))); } QString license = document->documentInfo()->aboutInfo("license"); if (!license.isEmpty()) { if (metaDataStore->containsEntry("rights")) { metaDataStore->removeEntry("rights"); } metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "rights", KisMetaData::Value(QVariant(license)))); } QString date = document->documentInfo()->aboutInfo("date"); if (!date.isEmpty() && !metaDataStore->containsEntry("rights")) { metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "date", KisMetaData::Value(QVariant(date)))); } } if (options.storeAuthor) { QString author = document->documentInfo()->authorInfo("creator"); if (!author.isEmpty()) { if (!document->documentInfo()->authorContactInfo().isEmpty()) { QString contact = document->documentInfo()->authorContactInfo().at(0); if (!contact.isEmpty()) { author = author+"("+contact+")"; } } if (metaDataStore->containsEntry("creator")) { metaDataStore->removeEntry("creator"); } metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "creator", KisMetaData::Value(QVariant(author)))); } } - KisImageBuilder_Result res = kpc.buildFile(io, l, options, metaDataStore.data()); - - if (res == KisImageBuilder_RESULT_OK) { - return KisImportExportFilter::OK; - } - dbgFile << " Result =" << res; - return KisImportExportFilter::InternalError; + KisImportExportErrorCode res = kpc.buildFile(io, l, options, metaDataStore.data()); + return res; } KisPropertiesConfigurationSP KisJPEGExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("progressive", false); cfg->setProperty("quality", 80); cfg->setProperty("forceSRGB", false); cfg->setProperty("saveProfile", true); cfg->setProperty("optimize", true); cfg->setProperty("smoothing", 0); cfg->setProperty("baseline", true); cfg->setProperty("subsampling", 0); cfg->setProperty("exif", true); cfg->setProperty("iptc", true); cfg->setProperty("xmp", true); cfg->setProperty("storeAuthor", false); cfg->setProperty("storeMetaData", false); KoColor fill_color(KoColorSpaceRegistry::instance()->rgb8()); fill_color = KoColor(); fill_color.fromQColor(Qt::white); QVariant v; v.setValue(fill_color); cfg->setProperty("transparencyFillcolor", v); cfg->setProperty("filters", ""); return cfg; } KisConfigWidget *KisJPEGExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisWdgOptionsJPEG(parent); } void KisJPEGExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ExifCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(CMYKAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "JPEG"); } KisWdgOptionsJPEG::KisWdgOptionsJPEG(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); metaDataFilters->setModel(&m_filterRegistryModel); qualityLevel->setRange(0, 100, 0); qualityLevel->setSuffix("%"); smoothLevel->setRange(0, 100, 0); smoothLevel->setSuffix("%"); } void KisWdgOptionsJPEG::setConfiguration(const KisPropertiesConfigurationSP cfg) { progressive->setChecked(cfg->getBool("progressive", false)); qualityLevel->setValue(cfg->getInt("quality", 80)); optimize->setChecked(cfg->getBool("optimize", true)); smoothLevel->setValue(cfg->getInt("smoothing", 0)); baseLineJPEG->setChecked(cfg->getBool("baseline", true)); subsampling->setCurrentIndex(cfg->getInt("subsampling", 0)); exif->setChecked(cfg->getBool("exif", true)); iptc->setChecked(cfg->getBool("iptc", true)); xmp->setChecked(cfg->getBool("xmp", true)); chkForceSRGB->setVisible(cfg->getBool("is_sRGB")); chkForceSRGB->setChecked(cfg->getBool("forceSRGB", false)); chkSaveProfile->setChecked(cfg->getBool("saveProfile", true)); KoColor background(KoColorSpaceRegistry::instance()->rgb8()); background.fromQColor(Qt::white); bnTransparencyFillColor->setDefaultColor(background); bnTransparencyFillColor->setColor(cfg->getColor("transparencyFillcolor", background)); chkAuthor->setChecked(cfg->getBool("storeAuthor", false)); chkMetaData->setChecked(cfg->getBool("storeMetaData", false)); m_filterRegistryModel.setEnabledFilters(cfg->getString("filters").split(',')); } KisPropertiesConfigurationSP KisWdgOptionsJPEG::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); QVariant transparencyFillcolor; transparencyFillcolor.setValue(bnTransparencyFillColor->color()); cfg->setProperty("progressive", progressive->isChecked()); cfg->setProperty("quality", (int)qualityLevel->value()); cfg->setProperty("forceSRGB", chkForceSRGB->isChecked()); cfg->setProperty("saveProfile", chkSaveProfile->isChecked()); cfg->setProperty("optimize", optimize->isChecked()); cfg->setProperty("smoothing", (int)smoothLevel->value()); cfg->setProperty("baseline", baseLineJPEG->isChecked()); cfg->setProperty("subsampling", subsampling->currentIndex()); cfg->setProperty("exif", exif->isChecked()); cfg->setProperty("iptc", iptc->isChecked()); cfg->setProperty("xmp", xmp->isChecked()); cfg->setProperty("transparencyFillcolor", transparencyFillcolor); cfg->setProperty("storeAuthor", chkAuthor->isChecked()); cfg->setProperty("storeMetaData", chkMetaData->isChecked()); QString enabledFilters; Q_FOREACH (const KisMetaData::Filter* filter, m_filterRegistryModel.enabledFilters()) { enabledFilters = enabledFilters + filter->id() + ','; } cfg->setProperty("filters", enabledFilters); return cfg; } #include diff --git a/plugins/impex/jpeg/kis_jpeg_export.h b/plugins/impex/jpeg/kis_jpeg_export.h index 624f9cea68..65ca4a44e9 100644 --- a/plugins/impex/jpeg/kis_jpeg_export.h +++ b/plugins/impex/jpeg/kis_jpeg_export.h @@ -1,58 +1,58 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_JPEG_EXPORT_H_ #define _KIS_JPEG_EXPORT_H_ #include #include #include #include "ui_kis_wdg_options_jpeg.h" #include #include class KisWdgOptionsJPEG : public KisConfigWidget, public Ui::WdgOptionsJPEG { Q_OBJECT public: KisWdgOptionsJPEG(QWidget *parent); void setConfiguration(const KisPropertiesConfigurationSP cfg) override; KisPropertiesConfigurationSP configuration() const override; private: KisMetaData::FilterRegistryModel m_filterRegistryModel; }; class KisJPEGExport : public KisImportExportFilter { Q_OBJECT public: KisJPEGExport(QObject *parent, const QVariantList &); ~KisJPEGExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/jpeg/kis_jpeg_import.cc b/plugins/impex/jpeg/kis_jpeg_import.cc index 4a80e3dc53..911c3c453d 100644 --- a/plugins/impex/jpeg/kis_jpeg_import.cc +++ b/plugins/impex/jpeg/kis_jpeg_import.cc @@ -1,80 +1,54 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_jpeg_import.h" #include #include #include #include #include #include #include "kis_jpeg_converter.h" K_PLUGIN_FACTORY_WITH_JSON(JPEGImportFactory, "krita_jpeg_import.json", registerPlugin();) KisJPEGImport::KisJPEGImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisJPEGImport::~KisJPEGImport() { } -KisImportExportFilter::ConversionStatus KisJPEGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisJPEGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { - KisJPEGConverter ib(document, batchMode()); - - switch (ib.buildImage(io)) { - case KisImageBuilder_RESULT_UNSUPPORTED: - case KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE: - return KisImportExportFilter::NotImplemented; - break; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - break; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_LOCAL: - return KisImportExportFilter::FileNotFound; - break; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - break; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InternalError; - break; - case KisImageBuilder_RESULT_OK: + KisImportExportErrorCode result = ib.buildImage(io); + if (result.isOk()) { document->setCurrentImage(ib.image()); - return KisImportExportFilter::OK; - default: - break; - } - - return KisImportExportFilter::InternalError; - + return result; } #include diff --git a/plugins/impex/jpeg/kis_jpeg_import.h b/plugins/impex/jpeg/kis_jpeg_import.h index 598237b859..63555c740b 100644 --- a/plugins/impex/jpeg/kis_jpeg_import.h +++ b/plugins/impex/jpeg/kis_jpeg_import.h @@ -1,36 +1,36 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_JPEG_IMPORT_H_ #define _KIS_JPEG_IMPORT_H_ #include #include class KisJPEGImport : public KisImportExportFilter { Q_OBJECT public: KisJPEGImport(QObject *parent, const QVariantList &); ~KisJPEGImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/jpeg/tests/data/incorrectFormatFile.txt b/plugins/impex/jpeg/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/jpeg/tests/data/readonlyFile.txt b/plugins/impex/jpeg/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/jpeg/tests/data/writeonlyFile.txt b/plugins/impex/jpeg/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp index 59da037645..7d84ef5cdc 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp +++ b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp @@ -1,55 +1,74 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_jpeg_test.h" #include #include #include "kisexiv2/kis_exiv2.h" #include "filestest.h" #include "jpeglib.h" #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif #ifndef JPEG_LIB_VERSION #error "JPEG_LIB_VERSION not set. libjpeg should set it." #endif +const QString JpegMimetype = "image/jpeg"; + void KisJpegTest::testFiles() { KisExiv2::initialize(); /** * Different versions of JPEG library may produce a bit different * result, so just compare in a weak way, i.e, only the size for real */ const int fuzziness = 1; const int maxNumFailingPixels = 2592 * 1952; // All pixels can be different... + const bool showDebug = false; // No need to write down all pixels that are different since all of them can be. if (JPEG_LIB_VERSION == 80){ - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), "_80", fuzziness, maxNumFailingPixels); + TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), "_80", fuzziness, maxNumFailingPixels, showDebug); }else { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), fuzziness, maxNumFailingPixels); + TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), fuzziness, maxNumFailingPixels, showDebug); } +} + +void KisJpegTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), JpegMimetype); +} + +void KisJpegTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), JpegMimetype); +} + + +void KisJpegTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), JpegMimetype); } KISTEST_MAIN(KisJpegTest) diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/jpeg/tests/kis_jpeg_test.h index 5c19b0cda6..dfc815be3c 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/jpeg/tests/kis_jpeg_test.h @@ -1,31 +1,34 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_JPEG_TEST_H_ #define _KIS_JPEG_TEST_H_ #include class KisJpegTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; #endif diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp index 5bce748908..807bfbe6de 100644 --- a/plugins/impex/kra/kra_converter.cpp +++ b/plugins/impex/kra/kra_converter.cpp @@ -1,372 +1,377 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kra_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char CURRENT_DTD_VERSION[] = "2.0"; KraConverter::KraConverter(KisDocument *doc) : m_doc(doc) , m_image(doc->savingImage()) { } KraConverter::~KraConverter() { delete m_store; delete m_kraSaver; delete m_kraLoader; } -KisImageBuilder_Result KraConverter::buildImage(QIODevice *io) +KisImportExportErrorCode KraConverter::buildImage(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Read, "", KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Not a valid Krita file")); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } bool success; { if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) KoXmlDocument doc; - bool ok = oldLoadAndParse(m_store, "root", doc); - if (ok) - ok = loadXML(doc, m_store); - if (!ok) { - return KisImageBuilder_RESULT_FAILURE; + KisImportExportErrorCode res = oldLoadAndParse(m_store, "root", doc); + if (res.isOk()) + res = loadXML(doc, m_store); + if (!res.isOk()) { + return res; } } else { errUI << "ERROR: No maindoc.xml" << endl; - m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'.")); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } if (m_store->hasFile("documentinfo.xml")) { KoXmlDocument doc; - if (oldLoadAndParse(m_store, "documentinfo.xml", doc)) { + if (oldLoadAndParse(m_store, "documentinfo.xml", doc).isOk()) { m_doc->documentInfo()->load(doc); } } success = completeLoading(m_store); } - return success ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE; + return success ? ImportExportCodes::OK : ImportExportCodes::Failure; } KisImageSP KraConverter::image() { return m_image; } vKisNodeSP KraConverter::activeNodes() { return m_activeNodes; } QList KraConverter::assistants() { return m_assistants; } -KisImageBuilder_Result KraConverter::buildFile(QIODevice *io, const QString &filename) +KisImportExportErrorCode KraConverter::buildFile(QIODevice *io, const QString &filename) { m_store = KoStore::createStore(io, KoStore::Write, m_doc->nativeFormatMimeType(), KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Could not create the file for saving")); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::CannotCreateFile; } - bool result = false; m_kraSaver = new KisKraSaver(m_doc, filename); - result = saveRootDocuments(m_store); + KisImportExportErrorCode resultCode = saveRootDocuments(m_store); - if (!result) { - return KisImageBuilder_RESULT_FAILURE; + if (!resultCode.isOk()) { + return resultCode; } + bool result; + result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true); if (!result) { qWarning() << "saving key frames failed"; } result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving()); if (!result) { qWarning() << "saving binary data failed"; } result = m_kraSaver->savePalettes(m_store, m_image, m_doc->url().toLocalFile()); if (!result) { qWarning() << "saving palettes data failed"; } if (!m_store->finalize()) { - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::Failure; } if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::Failure; } - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -bool KraConverter::saveRootDocuments(KoStore *store) +KisImportExportErrorCode KraConverter::saveRootDocuments(KoStore *store) { dbgFile << "Saving root"; if (store->open("root")) { KoStoreDevice dev(store); if (!saveToStream(&dev) || !store->close()) { dbgUI << "saveToStream failed"; - return false; + return ImportExportCodes::NoAccessToWrite; } } else { m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"))); - return false; + return ImportExportCodes::ErrorWhileWriting; } - bool success = false; + if (store->open("documentinfo.xml")) { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = m_doc->documentInfo()->save(doc); KoStoreDevice dev(store); QByteArray s = doc.toByteArray(); // this is already Utf8! - success = dev.write(s.data(), s.size()); + bool success = dev.write(s.data(), s.size()); + if (!success) { + return ImportExportCodes::ErrorWhileWriting; + } store->close(); + } else { + return ImportExportCodes::Failure; } if (store->open("preview.png")) { // ### TODO: missing error checking (The partition could be full!) - savePreview(store); + KisImportExportErrorCode result = savePreview(store); (void)store->close(); + if (!result.isOk()) { + return result; + } + } else { + return ImportExportCodes::Failure; } - dbgUI << "Saving done of url:" << m_doc->url().toLocalFile(); - // Success - return success; + return ImportExportCodes::OK; } bool KraConverter::saveToStream(QIODevice *dev) { QDomDocument doc = createDomDocument(); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open(QIODevice::WriteOnly); int nwritten = dev->write(s.data(), s.size()); if (nwritten != (int)s.size()) { warnUI << "wrote " << nwritten << "- expected" << s.size(); } return nwritten == (int)s.size(); } QDomDocument KraConverter::createDomDocument() { QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION); QDomElement root = doc.documentElement(); root.setAttribute("editor", "Krita"); root.setAttribute("syntaxVersion", "2"); root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false)); root.appendChild(m_kraSaver->saveXML(doc, m_image)); if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); } return doc; } -bool KraConverter::savePreview(KoStore *store) +KisImportExportErrorCode KraConverter::savePreview(KoStore *store) { QPixmap pix = m_doc->generatePreview(QSize(256, 256)); QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); if (preview.size() == QSize(0,0)) { QSize newSize = m_doc->savingImage()->bounds().size(); newSize.scale(QSize(256, 256), Qt::KeepAspectRatio); preview = QImage(newSize, QImage::Format_ARGB32); preview.fill(QColor(0, 0, 0, 0)); } KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { - return false; + return ImportExportCodes::NoAccessToWrite; } bool ret = preview.save(&io, "PNG"); io.close(); - return ret; + return ret ? ImportExportCodes::OK : ImportExportCodes::ErrorWhileWriting; } -bool KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc) +KisImportExportErrorCode KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc) { //dbgUI <<"Trying to open" << filename; if (!store->open(filename)) { warnUI << "Entry " << filename << " not found!"; - m_doc->setErrorMessage(i18n("Could not find %1", filename)); - return false; + return ImportExportCodes::FileNotExist; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); store->close(); if (!ok) { errUI << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; - m_doc->setErrorMessage(i18n("Parsing error in %1 at line %2, column %3\nError message: %4" - , filename , errorLine, errorColumn , - QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0))); - return false; + return ImportExportCodes::FileFormatIncorrect; } dbgUI << "File" << filename << " loaded and parsed"; - return true; + return ImportExportCodes::OK; } -bool KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) +KisImportExportErrorCode KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) { Q_UNUSED(store); KoXmlElement root; KoXmlNode node; if (doc.doctype().name() != "DOC") { - m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted")); - return false; + errUI << "The format is not supported or the file is corrupted"; + return ImportExportCodes::FileFormatIncorrect; } root = doc.documentElement(); int syntaxVersion = root.attribute("syntaxVersion", "3").toInt(); if (syntaxVersion > 2) { - m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion)); - return false; + errUI << "The file is too new for this version of Krita: " + syntaxVersion; + return ImportExportCodes::FormatFeaturesUnsupported; } if (!root.hasChildNodes()) { - m_doc->setErrorMessage(i18n("The file has no layers.")); - return false; + errUI << "The file has no layers."; + return ImportExportCodes::FileFormatIncorrect; } m_kraLoader = new KisKraLoader(m_doc, syntaxVersion); // reset the old image before loading the next one m_doc->setCurrentImage(0, false); for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isElement()) { if (node.nodeName() == "IMAGE") { KoXmlElement elem = node.toElement(); if (!(m_image = m_kraLoader->loadXML(elem))) { + if (m_kraLoader->errorMessages().isEmpty()) { - m_doc->setErrorMessage(i18n("Unknown error.")); + errUI << "Unknown error while opening the .kra file."; } else { - m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); + errUI << m_kraLoader->errorMessages().join("\n"); } - return false; + return ImportExportCodes::Failure; } // HACK ALERT! m_doc->hackPreliminarySetImage(m_image); - return true; + return ImportExportCodes::OK; } else { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("The file does not contain an image.")); } - return false; + return ImportExportCodes::FileFormatIncorrect; } } } - return false; + return ImportExportCodes::Failure; } bool KraConverter::completeLoading(KoStore* store) { if (!m_image) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } m_image->blockUpdates(); QString layerPathName = m_kraLoader->imageName(); if (!m_store->hasDirectory(layerPathName)) { // We might be hitting an encoding problem. Get the only folder in the toplevel Q_FOREACH (const QString &entry, m_store->directoryList()) { if (entry.contains("/layers/")) { layerPathName = entry.split("/layers/").first(); m_store->setSubstitution(m_kraLoader->imageName(), layerPathName); break; } } } m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true); m_kraLoader->loadPalettes(store, m_doc); m_image->unblockUpdates(); if (!m_kraLoader->warningMessages().isEmpty()) { // warnings do not interrupt loading process, so we do not return here m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n")); } m_activeNodes = m_kraLoader->selectedNodes(); m_assistants = m_kraLoader->assistants(); return true; } void KraConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/kra/kra_converter.h b/plugins/impex/kra/kra_converter.h index ff5b50a331..46a634e9d7 100644 --- a/plugins/impex/kra/kra_converter.h +++ b/plugins/impex/kra/kra_converter.h @@ -1,80 +1,80 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KRA_CONVERTER_H_ #define _KRA_CONVERTER_H_ #include #include #include #include #include #include #include #include class KisDocument; class KraConverter : public QObject { Q_OBJECT public: KraConverter(KisDocument *doc); ~KraConverter() override; - KisImageBuilder_Result buildImage(QIODevice *io); - KisImageBuilder_Result buildFile(QIODevice *io, const QString &filename); + KisImportExportErrorCode buildImage(QIODevice *io); + KisImportExportErrorCode buildFile(QIODevice *io, const QString &filename); /** * Retrieve the constructed image */ KisImageSP image(); vKisNodeSP activeNodes(); QList assistants(); public Q_SLOTS: virtual void cancel(); private: - bool saveRootDocuments(KoStore *store); + KisImportExportErrorCode saveRootDocuments(KoStore *store); bool saveToStream(QIODevice *dev); QDomDocument createDomDocument(); - bool savePreview(KoStore *store); - bool oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc); - bool loadXML(const KoXmlDocument &doc, KoStore *store); + KisImportExportErrorCode savePreview(KoStore *store); + KisImportExportErrorCode oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc); + KisImportExportErrorCode loadXML(const KoXmlDocument &doc, KoStore *store); bool completeLoading(KoStore *store); KisDocument *m_doc {0}; KisImageSP m_image; vKisNodeSP m_activeNodes; QList m_assistants; bool m_stop {false}; KoStore *m_store {0}; KisKraSaver *m_kraSaver {0}; KisKraLoader *m_kraLoader {0}; }; #endif diff --git a/plugins/impex/kra/kra_export.cpp b/plugins/impex/kra/kra_export.cpp index 69a1a6984b..4e9c4d8a7e 100644 --- a/plugins/impex/kra/kra_export.cpp +++ b/plugins/impex/kra/kra_export.cpp @@ -1,83 +1,78 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kra_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kra_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_kra_export.json", registerPlugin();) KraExport::KraExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KraExport::~KraExport() { } -KisImportExportFilter::ConversionStatus KraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisImageSP image = document->savingImage(); - KIS_ASSERT_RECOVER_RETURN_VALUE(image, CreationError); + KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError); KraConverter kraConverter(document); - KisImageBuilder_Result res = kraConverter.buildFile(io, filename()); - - if (res == KisImageBuilder_RESULT_OK) { - dbgFile << "KraExport::convert success !"; - return KisImportExportFilter::OK; - } + KisImportExportErrorCode res = kraConverter.buildFile(io, filename()); dbgFile << "KraExport::convert result =" << res; - return KisImportExportFilter::InternalError; + return res; } void KraExport::initializeCapabilities() { // Kra supports everything, by definition KisExportCheckFactory *factory = 0; Q_FOREACH(const QString &id, KisExportCheckRegistry::instance()->keys()) { factory = KisExportCheckRegistry::instance()->get(id); addCapability(factory->create(KisExportCheckBase::SUPPORTED)); } } #include diff --git a/plugins/impex/kra/kra_export.h b/plugins/impex/kra/kra_export.h index 2dc08f2cb7..bffdb4f824 100644 --- a/plugins/impex/kra/kra_export.h +++ b/plugins/impex/kra/kra_export.h @@ -1,39 +1,39 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KRA_EXPORT_H_ #define _KRA_EXPORT_H_ #include #include class KraExport : public KisImportExportFilter { Q_OBJECT public: KraExport(QObject *parent, const QVariantList &); ~KraExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/kra/kra_import.cpp b/plugins/impex/kra/kra_import.cpp index bbc6bcaff2..3f85a6835b 100644 --- a/plugins/impex/kra/kra_import.cpp +++ b/plugins/impex/kra/kra_import.cpp @@ -1,77 +1,47 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kra_import.h" #include #include #include #include #include "kra_converter.h" K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_kra_import.json", registerPlugin();) KraImport::KraImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KraImport::~KraImport() { } -KisImportExportFilter::ConversionStatus KraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KraConverter kraConverter(document); - switch (kraConverter.buildImage(io)) { - case KisImageBuilder_RESULT_UNSUPPORTED: - return KisImportExportFilter::NotImplemented; - break; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - break; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_LOCAL: - return KisImportExportFilter::FileNotFound; - break; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - break; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InvalidFormat; - break; - case KisImageBuilder_RESULT_OK: - document->setCurrentImage(kraConverter.image()); - if (kraConverter.activeNodes().size() > 0) { - document->setPreActivatedNode(kraConverter.activeNodes()[0]); - } - if (kraConverter.assistants().size() > 0) { - document->setAssistants(kraConverter.assistants()); - } - return KisImportExportFilter::OK; - default: - break; - } - return KisImportExportFilter::InternalError; + return kraConverter.buildImage(io); } #include diff --git a/plugins/impex/kra/kra_import.h b/plugins/impex/kra/kra_import.h index f5eb04a2db..e921cdd060 100644 --- a/plugins/impex/kra/kra_import.h +++ b/plugins/impex/kra/kra_import.h @@ -1,37 +1,37 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KRA_IMPORT_H_ #define KRA_IMPORT_H_ #include #include class KraImport : public KisImportExportFilter { Q_OBJECT public: KraImport(QObject *parent, const QVariantList &); ~KraImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/libkra/tests/data/incorrectFormatFile.txt b/plugins/impex/libkra/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/libkra/tests/data/readonlyFile.txt b/plugins/impex/libkra/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/libkra/tests/data/writeonlyFile.txt b/plugins/impex/libkra/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/libkra/tests/kis_kra_loader_test.cpp b/plugins/impex/libkra/tests/kis_kra_loader_test.cpp index 10350d829c..500295dcd7 100644 --- a/plugins/impex/libkra/tests/kis_kra_loader_test.cpp +++ b/plugins/impex/libkra/tests/kis_kra_loader_test.cpp @@ -1,173 +1,192 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_loader_test.h" #include #include #include #include #include #include #include "kis_image.h" #include "testutil.h" #include "KisPart.h" #include #include #include "kis_image_animation_interface.h" #include "kis_keyframe_channel.h" #include "kis_time_range.h" +#include + #include + +const QString KraMimetype = "application/x-krita"; + void KisKraLoaderTest::initTestCase() { KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); } void KisKraLoaderTest::testLoading() { QScopedPointer doc(KisPart::instance()->createDocument()); doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test.kra"); KisImageSP image = doc->image(); image->lock(); QCOMPARE(image->nlayers(), 12); QCOMPARE(doc->documentInfo()->aboutInfo("title"), QString("test image for loading")); QCOMPARE(image->height(), 753); QCOMPARE(image->width(), 1000); QCOMPARE(image->colorSpace()->id(), KoColorSpaceRegistry::instance()->rgb8()->id()); KisNodeSP node = image->root()->firstChild(); QVERIFY(node); QCOMPARE(node->name(), QString("Background")); QVERIFY(node->inherits("KisPaintLayer")); node = node->nextSibling(); QVERIFY(node); QCOMPARE(node->name(), QString("Group 1")); QVERIFY(node->inherits("KisGroupLayer")); QCOMPARE((int) node->childCount(), 2); } void testObligeSingleChildImpl(bool transpDefaultPixel) { QString id = !transpDefaultPixel ? "single_layer_no_channel_flags_nontransp_def_pixel.kra" : "single_layer_no_channel_flags_transp_def_pixel.kra"; QString fileName = TestUtil::fetchDataFileLazy(id); QScopedPointer doc(KisPart::instance()->createDocument()); const bool result = doc->loadNativeFormat(fileName); QVERIFY(result); KisImageSP image = doc->image(); QVERIFY(image); QCOMPARE(image->nlayers(), 2); KisNodeSP root = image->root(); KisNodeSP child = root->firstChild(); QVERIFY(child); QCOMPARE(root->original(), root->projection()); if (transpDefaultPixel) { QCOMPARE(root->original(), child->projection()); } else { QVERIFY(root->original() != child->projection()); } } void KisKraLoaderTest::testObligeSingleChild() { testObligeSingleChildImpl(true); } void KisKraLoaderTest::testObligeSingleChildNonTranspPixel() { testObligeSingleChildImpl(false); } void KisKraLoaderTest::testLoadAnimated() { QScopedPointer doc(KisPart::instance()->createDocument()); doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test_animation.kra"); KisImageSP image = doc->image(); KisNodeSP node1 = image->root()->firstChild(); KisNodeSP node2 = node1->nextSibling(); QVERIFY(node1->inherits("KisPaintLayer")); QVERIFY(node2->inherits("KisPaintLayer")); KisPaintLayerSP layer1 = qobject_cast(node1.data()); KisPaintLayerSP layer2 = qobject_cast(node2.data()); QVERIFY(layer1->isAnimated()); QVERIFY(!layer2->isAnimated()); KisKeyframeChannel *channel1 = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id()); QVERIFY(channel1); QCOMPARE(channel1->keyframeCount(), 3); QCOMPARE(image->animationInterface()->framerate(), 17); QCOMPARE(image->animationInterface()->fullClipRange(), KisTimeRange::fromTime(15, 45)); QCOMPARE(image->animationInterface()->currentTime(), 19); KisPaintDeviceSP dev = layer1->paintDevice(); const KoColorSpace *cs = dev->colorSpace(); KoColor transparent(Qt::transparent, cs); KoColor white(Qt::white, cs); KoColor red(Qt::red, cs); image->animationInterface()->switchCurrentTimeAsync(0); image->waitForDone(); QCOMPARE(dev->exactBounds(), QRect(506, 378, 198, 198)); QCOMPARE(dev->x(), -26); QCOMPARE(dev->y(), -128); QCOMPARE(dev->defaultPixel(), transparent); image->animationInterface()->switchCurrentTimeAsync(20); image->waitForDone(); QCOMPARE(dev->nonDefaultPixelArea(), QRect(615, 416, 129, 129)); QCOMPARE(dev->x(), 502); QCOMPARE(dev->y(), 224); QCOMPARE(dev->defaultPixel(), white); image->animationInterface()->switchCurrentTimeAsync(30); image->waitForDone(); QCOMPARE(dev->nonDefaultPixelArea(), QRect(729, 452, 45, 44)); QCOMPARE(dev->x(), 645); QCOMPARE(dev->y(), -10); QCOMPARE(dev->defaultPixel(), red); } + +void KisKraLoaderTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), KraMimetype); +} + + +void KisKraLoaderTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), KraMimetype); +} + + + KISTEST_MAIN(KisKraLoaderTest) diff --git a/plugins/impex/libkra/tests/kis_kra_loader_test.h b/plugins/impex/libkra/tests/kis_kra_loader_test.h index 88da2d5448..99a1caf0a4 100644 --- a/plugins/impex/libkra/tests/kis_kra_loader_test.h +++ b/plugins/impex/libkra/tests/kis_kra_loader_test.h @@ -1,37 +1,42 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_KRA_LOADER_TEST_H #define KIS_KRA_LOADER_TEST_H #include class KisKraLoaderTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testLoading(); void testObligeSingleChild(); void testObligeSingleChildNonTranspPixel(); void testLoadAnimated(); + + void testImportFromWriteonly(); + void testImportIncorrectFormat(); + + }; #endif diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp index bd7fbfb1ee..e9afb6f14c 100644 --- a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp +++ b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp @@ -1,543 +1,552 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_saver_test.h" #include #include #include #include #include #include #include "filter/kis_filter_registry.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter.h" #include "kis_image.h" #include "kis_pixel_selection.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_adjustment_layer.h" #include "kis_shape_layer.h" #include "kis_filter_mask.h" #include "kis_transparency_mask.h" #include "kis_selection_mask.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "kis_shape_selection.h" #include "util.h" #include "testutil.h" #include "kis_keyframe_channel.h" #include "kis_image_animation_interface.h" #include "kis_layer_properties_icons.h" #include "kis_transform_mask_params_interface.h" #include #include #include +#include + +const QString KraMimetype = "application/x-krita"; void KisKraSaverTest::initTestCase() { KoResourcePaths::addResourceDir("ko_patterns", QString(SYSTEM_RESOURCES_DATA_DIR) + "/patterns"); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); } void KisKraSaverTest::testCrashyShapeLayer() { /** * KisShapeLayer used to call setImage from its destructor and * therefore causing an infinite recursion (when at least one transparency * mask was preset. This testcase just checks that. */ //QScopedPointer doc(createCompleteDocument(true)); //Q_UNUSED(doc); } void KisKraSaverTest::testRoundTrip() { KisDocument* doc = createCompleteDocument(); KoColor bgColor(Qt::red, doc->image()->colorSpace()); doc->image()->setDefaultProjectionColor(bgColor); doc->exportDocumentSync(QUrl::fromLocalFile("roundtriptest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); bool result = doc2->loadNativeFormat("roundtriptest.kra"); QVERIFY(result); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); // check whether the BG color is saved correctly QCOMPARE(doc2->image()->defaultProjectionColor(), bgColor); // test round trip of a transform mask KisNode* tnode = TestUtil::findNode(doc2->image()->rootLayer(), "testTransformMask").data(); QVERIFY(tnode); KisTransformMask *tmask = dynamic_cast(tnode); QVERIFY(tmask); KisDumbTransformMaskParams *params = dynamic_cast(tmask->transformParams().data()); QVERIFY(params); QTransform t = params->testingGetTransform(); QCOMPARE(t, createTestingTransform()); delete doc2; delete doc; } void KisKraSaverTest::testSaveEmpty() { KisDocument* doc = createEmptyDocument(); doc->exportDocumentSync(QUrl::fromLocalFile("emptytest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); doc2->loadNativeFormat("emptytest.kra"); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); delete doc2; delete doc; } #include void testRoundTripFillLayerImpl(const QString &testName, KisFilterConfigurationSP config) { TestUtil::ReferenceImageChecker chk(testName, "fill_layer"); chk.setFuzzy(2); QScopedPointer doc(KisPart::instance()->createDocument()); // mask parent should be destructed before the document! QRect refRect(0,0,512,512); TestUtil::MaskParent p(refRect); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KisSelectionSP selection; KisGeneratorLayerSP glayer = new KisGeneratorLayer(p.image, "glayer", config, selection); p.image->addNode(glayer, p.image->root(), KisNodeSP()); glayer->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_fill_layer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_fill_layer_test.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "01_fill_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripFillLayerColor() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("color"); Q_ASSERT(generator); // warning: we pass null paint device to the default constructed value KisFilterConfigurationSP config = generator->factoryConfiguration(); Q_ASSERT(config); QVariant v; v.setValue(KoColor(Qt::red, cs)); config->setProperty("color", v); testRoundTripFillLayerImpl("fill_layer_color", config); } void KisKraSaverTest::testRoundTripFillLayerPattern() { KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("pattern"); QVERIFY(generator); // warning: we pass null paint device to the default constructed value KisFilterConfigurationSP config = generator->factoryConfiguration(); QVERIFY(config); QVariant v; v.setValue(QString("11_drawed_furry.png")); config->setProperty("pattern", v); testRoundTripFillLayerImpl("fill_layer_pattern", config); } #include "kis_psd_layer_style.h" void KisKraSaverTest::testRoundTripLayerStyles() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "layer_styles"); QRect imageRect(0,0,512,512); // the document should be created before the image! QScopedPointer doc(KisPart::instance()->createDocument()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisPaintLayerSP layer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisPaintLayerSP layer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); image->addNode(layer1); image->addNode(layer2); image->addNode(layer3); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); layer1->paintDevice()->fill(QRect(100, 100, 100, 100), KoColor(Qt::red, cs)); layer2->paintDevice()->fill(QRect(200, 200, 100, 100), KoColor(Qt::green, cs)); layer3->paintDevice()->fill(QRect(300, 300, 100, 100), KoColor(Qt::blue, cs)); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setAngle(-90); style->dropShadow()->setUseGlobalLight(false); layer1->setLayerStyle(style->clone()); style->dropShadow()->setAngle(180); style->dropShadow()->setUseGlobalLight(true); layer2->setLayerStyle(style->clone()); style->dropShadow()->setAngle(90); style->dropShadow()->setUseGlobalLight(false); layer3->setLayerStyle(style->clone()); image->initialRefreshGraph(); chk.checkImage(image, "00_initial_layers"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_layer_styles.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_layer_styles.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "00_initial_layers"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripAnimation() { QScopedPointer doc(KisPart::instance()->createDocument()); QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); image->addNode(layer1); layer1->paintDevice()->fill(QRect(100, 100, 50, 50), KoColor(Qt::black, cs)); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::red, cs)); KUndo2Command parentCommand; layer1->enableAnimation(); KisKeyframeChannel *rasterChannel = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); QVERIFY(rasterChannel); rasterChannel->addKeyframe(10, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(10); image->waitForDone(); layer1->paintDevice()->fill(QRect(200, 50, 10, 10), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(25, 15); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::green, cs)); rasterChannel->addKeyframe(20, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(20); image->waitForDone(); layer1->paintDevice()->fill(QRect(150, 200, 30, 30), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(100, 50); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::blue, cs)); QVERIFY(!layer1->useInTimeline()); layer1->setUseInTimeline(true); doc->setCurrentImage(image); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_animation.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_animation.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild(); QVERIFY(node->inherits("KisPaintLayer")); KisPaintLayerSP layer2 = qobject_cast(node.data()); cs = layer2->paintDevice()->colorSpace(); QCOMPARE(image2->animationInterface()->currentTime(), 20); KisKeyframeChannel *channel = layer2->getKeyframeChannel(KisKeyframeChannel::Content.id()); QVERIFY(channel); QCOMPARE(channel->keyframeCount(), 3); image2->animationInterface()->switchCurrentTimeAsync(0); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(64, 64, 128, 128)); QCOMPARE(layer2->paintDevice()->x(), 0); QCOMPARE(layer2->paintDevice()->y(), 0); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::red, cs)); image2->animationInterface()->switchCurrentTimeAsync(10); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(217, 15, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 25); QCOMPARE(layer2->paintDevice()->y(), 15); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::green, cs)); image2->animationInterface()->switchCurrentTimeAsync(20); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(228, 242, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 100); QCOMPARE(layer2->paintDevice()->y(), 50); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::blue, cs)); QVERIFY(layer2->useInTimeline()); } #include "lazybrush/kis_lazy_fill_tools.h" void KisKraSaverTest::testRoundTripColorizeMask() { QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->rgb16(); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); doc->setCurrentImage(image); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, weirdCS); image->addNode(layer1); KisColorizeMaskSP mask = new KisColorizeMask(); image->addNode(mask, layer1); mask->initializeCompositeOp(); delete mask->setColorSpace(layer1->colorSpace()); { KisPaintDeviceSP key1 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key1->fill(QRect(50,50,10,20), KoColor(Qt::black, key1->colorSpace())); mask->testingAddKeyStroke(key1, KoColor(Qt::green, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key1, refRect, "key1", "dd"); } { KisPaintDeviceSP key2 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key2->fill(QRect(150,50,10,20), KoColor(Qt::black, key2->colorSpace())); mask->testingAddKeyStroke(key2, KoColor(Qt::red, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key2, refRect, "key2", "dd"); } { KisPaintDeviceSP key3 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key3->fill(QRect(0,0,10,10), KoColor(Qt::black, key3->colorSpace())); mask->testingAddKeyStroke(key3, KoColor(Qt::blue, layer1->colorSpace()), true); // KIS_DUMP_DEVICE_2(key3, refRect, "key3", "dd"); } KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, false, image); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_colorize.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_colorize.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild()->firstChild(); KisColorizeMaskSP mask2 = dynamic_cast(node.data()); QVERIFY(mask2); QCOMPARE(mask2->compositeOpId(), mask->compositeOpId()); QCOMPARE(mask2->colorSpace(), mask->colorSpace()); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(), false); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(), false); QList strokes = mask->fetchKeyStrokesDirect(); qDebug() << ppVar(strokes.size()); QCOMPARE(strokes[0].dev->exactBounds(), QRect(50,50,10,20)); QCOMPARE(strokes[0].isTransparent, false); QCOMPARE(strokes[0].color.colorSpace(), weirdCS); QCOMPARE(strokes[1].dev->exactBounds(), QRect(150,50,10,20)); QCOMPARE(strokes[1].isTransparent, false); QCOMPARE(strokes[1].color.colorSpace(), weirdCS); QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,10,10)); QCOMPARE(strokes[2].isTransparent, true); QCOMPARE(strokes[2].color.colorSpace(), weirdCS); } #include void KisKraSaverTest::testRoundTripShapeLayer() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_layer"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75); shapeLayer->addShape(path); p.image->addNode(shapeLayer); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapelayer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapelayer_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "01_shape_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripShapeSelection() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_selection"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); doc->setCurrentImage(p.image); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); p.layer->paintDevice()->setDefaultPixel(KoColor(Qt::green, p.layer->colorSpace())); KisSelectionSP selection = new KisSelection(p.layer->paintDevice()->defaultBounds()); KisShapeSelection *shapeSelection = new KisShapeSelection(doc->shapeController(), p.image, selection); selection->setShapeSelection(shapeSelection); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); shapeSelection->addShape(path); KisTransparencyMaskSP tmask = new KisTransparencyMask(); tmask->setSelection(selection); p.image->addNode(tmask, p.layer); tmask->setDirty(p.image->bounds()); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_shape_selection"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapeselection_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapeselection_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "00_initial_shape_selection"); KisNodeSP node = doc2->image()->root()->firstChild()->firstChild(); KisTransparencyMask *newMask = dynamic_cast(node.data()); QVERIFY(newMask); QVERIFY(newMask->selection()->hasShapeSelection()); QVERIFY(chk.testPassed()); } + +void KisKraSaverTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), KraMimetype); +} + KISTEST_MAIN(KisKraSaverTest) diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.h b/plugins/impex/libkra/tests/kis_kra_saver_test.h index 04ff99c8d5..4a6c1310bb 100644 --- a/plugins/impex/libkra/tests/kis_kra_saver_test.h +++ b/plugins/impex/libkra/tests/kis_kra_saver_test.h @@ -1,51 +1,53 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_KRA_SAVER_TEST_H #define KIS_KRA_SAVER_TEST_H #include class KisKraSaverTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testCrashyShapeLayer(); // XXX: Also test roundtripping of metadata void testRoundTrip(); void testSaveEmpty(); void testRoundTripFillLayerColor(); void testRoundTripFillLayerPattern(); void testRoundTripLayerStyles(); void testRoundTripAnimation(); void testRoundTripColorizeMask(); void testRoundTripShapeLayer(); void testRoundTripShapeSelection(); + void testExportToReadonly(); + }; #endif diff --git a/plugins/impex/ora/CMakeLists.txt b/plugins/impex/ora/CMakeLists.txt index 6cedf854e4..a476f422b4 100644 --- a/plugins/impex/ora/CMakeLists.txt +++ b/plugins/impex/ora/CMakeLists.txt @@ -1,32 +1,34 @@ +add_subdirectory(tests) + set(libkritaconverter_LIB_SRCS ora_converter.cpp kis_open_raster_load_context.cpp kis_open_raster_save_context.cpp kis_open_raster_stack_load_visitor.cpp kis_open_raster_stack_save_visitor.cpp ) set(kritaoraimport_SOURCES ora_import.cc ${libkritaconverter_LIB_SRCS} ) add_library(kritaoraimport MODULE ${kritaoraimport_SOURCES}) target_link_libraries(kritaoraimport kritaui ) install(TARGETS kritaoraimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritaoraexport_SOURCES ora_export.cc ${libkritaconverter_LIB_SRCS} ) add_library(kritaoraexport MODULE ${kritaoraexport_SOURCES}) target_link_libraries(kritaoraexport kritaui kritaimpex) install(TARGETS kritaoraexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_ora.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/ora/ora_converter.cpp b/plugins/impex/ora/ora_converter.cpp index 044b41fdf1..1df32a0c15 100644 --- a/plugins/impex/ora/ora_converter.cpp +++ b/plugins/impex/ora/ora_converter.cpp @@ -1,116 +1,117 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ora_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include "kis_open_raster_load_context.h" #include "kis_open_raster_save_context.h" OraConverter::OraConverter(KisDocument *doc) : m_doc(doc) , m_stop(false) { } OraConverter::~OraConverter() { } -KisImageBuilder_Result OraConverter::buildImage(QIODevice *io) +KisImportExportErrorCode OraConverter::buildImage(QIODevice *io) { KoStore* store = KoStore::createStore(io, KoStore::Read, "image/openraster", KoStore::Zip); if (!store) { delete store; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::Failure; } KisOpenRasterLoadContext olc(store); KisOpenRasterStackLoadVisitor orslv(m_doc->createUndoStore(), &olc); orslv.loadImage(); m_image = orslv.image(); m_activeNodes = orslv.activeNodes(); delete store; - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } KisImageSP OraConverter::image() { return m_image; } vKisNodeSP OraConverter::activeNodes() { return m_activeNodes; } -KisImageBuilder_Result OraConverter::buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes) +KisImportExportErrorCode OraConverter::buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes) { // Open file for writing KoStore* store = KoStore::createStore(io, KoStore::Write, "image/openraster", KoStore::Zip); if (!store) { - return KisImageBuilder_RESULT_FAILURE; + delete store; + return ImportExportCodes::Failure; } KisOpenRasterSaveContext osc(store); KisOpenRasterStackSaveVisitor orssv(&osc, activeNodes); image->rootLayer()->accept(orssv); if (store->open("Thumbnails/thumbnail.png")) { QSize previewSize = image->bounds().size(); previewSize.scale(QSize(256,256), Qt::KeepAspectRatio); QImage preview = image->convertToQImage(previewSize, 0); KoStoreDevice io(store); if (io.open(QIODevice::WriteOnly)) { preview.save(&io, "PNG"); } io.close(); store->close(); } KisPaintDeviceSP dev = image->projection(); KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store); delete store; - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } void OraConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/ora/ora_converter.h b/plugins/impex/ora/ora_converter.h index 33f8c12d8c..5e1c2f0c27 100644 --- a/plugins/impex/ora/ora_converter.h +++ b/plugins/impex/ora/ora_converter.h @@ -1,53 +1,53 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ORA_CONVERTER_H_ #define _ORA_CONVERTER_H_ #include #include #include "kis_png_converter.h" #include "kis_types.h" class KisDocument; class OraConverter : public QObject { Q_OBJECT public: OraConverter(KisDocument *doc); ~OraConverter() override; public: - KisImageBuilder_Result buildImage(QIODevice *io); - KisImageBuilder_Result buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes); + KisImportExportErrorCode buildImage(QIODevice *io); + KisImportExportErrorCode buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes); /** * Retrieve the constructed image */ KisImageSP image(); vKisNodeSP activeNodes(); public Q_SLOTS: virtual void cancel(); private: KisImageSP m_image; KisDocument *m_doc; vKisNodeSP m_activeNodes; bool m_stop; }; #endif diff --git a/plugins/impex/ora/ora_export.cc b/plugins/impex/ora/ora_export.cc index ed8ec1328c..711dbbbe79 100644 --- a/plugins/impex/ora/ora_export.cc +++ b/plugins/impex/ora/ora_export.cc @@ -1,109 +1,103 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ora_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ora_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_ora_export.json", registerPlugin();) OraExport::OraExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } OraExport::~OraExport() { } bool hasShapeLayerChild(KisNodeSP node) { if (!node) return false; Q_FOREACH (KisNodeSP child, node->childNodes(QStringList(), KoProperties())) { if (child->inherits("KisShapeLayer") || child->inherits("KisGeneratorLayer") || child->inherits("KisCloneLayer")) { return true; } else { if (hasShapeLayerChild(child)) { return true; } } } return false; } -KisImportExportFilter::ConversionStatus OraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode OraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisImageSP image = document->savingImage(); Q_CHECK_PTR(image); OraConverter oraConverter(document); - KisImageBuilder_Result res; - - if ((res = oraConverter.buildFile(io, image, {document->preActivatedNode()})) == KisImageBuilder_RESULT_OK) { - dbgFile << "success !"; - return KisImportExportFilter::OK; - } - dbgFile << " Result =" << res; - return KisImportExportFilter::InternalError; + KisImportExportErrorCode res = oraConverter.buildFile(io, image, {document->preActivatedNode()}); + return res; } void OraExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisAdjustmentLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelHomogenousCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "OpenRaster"); } #include diff --git a/plugins/impex/ora/ora_export.h b/plugins/impex/ora/ora_export.h index a3a614475c..2e06e207f3 100644 --- a/plugins/impex/ora/ora_export.h +++ b/plugins/impex/ora/ora_export.h @@ -1,36 +1,36 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ORA_EXPORT_H_ #define _ORA_EXPORT_H_ #include #include class OraExport : public KisImportExportFilter { Q_OBJECT public: OraExport(QObject *parent, const QVariantList &); ~OraExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/ora/ora_import.cc b/plugins/impex/ora/ora_import.cc index 635b88fe2d..2d49dee39c 100644 --- a/plugins/impex/ora/ora_import.cc +++ b/plugins/impex/ora/ora_import.cc @@ -1,76 +1,52 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ora_import.h" #include #include #include #include #include "ora_converter.h" K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_ora_import.json", registerPlugin();) OraImport::OraImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } OraImport::~OraImport() { } -KisImportExportFilter::ConversionStatus OraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode OraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { OraConverter oraConverter(document); - - - switch (oraConverter.buildImage(io)) { - case KisImageBuilder_RESULT_UNSUPPORTED: - return KisImportExportFilter::NotImplemented; - break; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - break; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_LOCAL: - return KisImportExportFilter::FileNotFound; - break; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - break; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InternalError; - break; - case KisImageBuilder_RESULT_OK: + KisImportExportErrorCode result = oraConverter.buildImage(io); + if (result.isOk()) { document->setCurrentImage(oraConverter.image()); if (oraConverter.activeNodes().size() > 0) { document->setPreActivatedNode(oraConverter.activeNodes()[0]); } - return KisImportExportFilter::OK; - default: - break; } - - - return KisImportExportFilter::InternalError; + return result; } #include diff --git a/plugins/impex/ora/ora_import.h b/plugins/impex/ora/ora_import.h index bc40596deb..7e8b434752 100644 --- a/plugins/impex/ora/ora_import.h +++ b/plugins/impex/ora/ora_import.h @@ -1,35 +1,35 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ORA_IMPORT_H_ #define ORA_IMPORT_H_ #include #include class OraImport : public KisImportExportFilter { Q_OBJECT public: OraImport(QObject *parent, const QVariantList &); ~OraImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/ora/tests/CMakeLists.txt b/plugins/impex/ora/tests/CMakeLists.txt new file mode 100644 index 0000000000..40a1fd500f --- /dev/null +++ b/plugins/impex/ora/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisOraTest.cpp + TEST_NAME KisOraTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/ora/tests/KisOraTest.cpp similarity index 64% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/ora/tests/KisOraTest.cpp index 6ca66688f8..39931639a0 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/ora/tests/KisOraTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "kis_svg_test.h" +#include "KisOraTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString OraMimetype = "image/openraster"; + + + +void KisOraTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), OraMimetype); +} + + +void KisOraTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), OraMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisOraTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), OraMimetype); +} + + + +KISTEST_MAIN(KisOraTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/ora/tests/KisOraTest.h similarity index 74% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/ora/tests/KisOraTest.h index 5c19b0cda6..a044b20b25 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/ora/tests/KisOraTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _KIS_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_ORA_TEST_H_ +#define _KIS_ORA_TEST_H_ #include -class KisJpegTest : public QObject +class KisOraTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_ORA_TEST_H_ + diff --git a/plugins/impex/ora/tests/data/incorrectFormatFile.txt b/plugins/impex/ora/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/ora/tests/data/readonlyFile.txt b/plugins/impex/ora/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/ora/tests/data/writeonlyFile.txt b/plugins/impex/ora/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/pdf/CMakeLists.txt b/plugins/impex/pdf/CMakeLists.txt index d3ada35792..320da291a4 100644 --- a/plugins/impex/pdf/CMakeLists.txt +++ b/plugins/impex/pdf/CMakeLists.txt @@ -1,10 +1,12 @@ +add_subdirectory(tests) + set(kritapdfimport_SOURCES kis_pdf_import.cpp kis_pdf_import_widget.cpp ) ki18n_wrap_ui(kritapdfimport_SOURCES pdfimportwidgetbase.ui ) add_library(kritapdfimport MODULE ${kritapdfimport_SOURCES}) target_link_libraries(kritapdfimport kritaui Poppler::Qt5) install(TARGETS kritapdfimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install(PROGRAMS krita_pdf.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/pdf/kis_pdf_import.cpp b/plugins/impex/pdf/kis_pdf_import.cpp index 7854cc0627..a30e49cd21 100644 --- a/plugins/impex/pdf/kis_pdf_import.cpp +++ b/plugins/impex/pdf/kis_pdf_import.cpp @@ -1,135 +1,136 @@ /* * Copyright (c) 2006 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_pdf_import.h" // poppler's headers #include // Qt's headers #include #include #include #include #include // KDE's headers #include #include #include #include #include // calligra's headers #include #include #include #include // krita's headers #include #include #include #include #include // plugins's headers #include "kis_pdf_import_widget.h" +#include K_PLUGIN_FACTORY_WITH_JSON(PDFImportFactory, "krita_pdf_import.json", registerPlugin();) KisPDFImport::KisPDFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPDFImport::~KisPDFImport() { } -KisPDFImport::ConversionStatus KisPDFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisPDFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { Poppler::Document* pdoc = Poppler::Document::loadFromData(io->readAll()); if (!pdoc) { dbgFile << "Error when reading the PDF"; - return KisImportExportFilter::StorageCreationError; + return ImportExportCodes::ErrorWhileReading; } pdoc->setRenderHint(Poppler::Document::Antialiasing, true); pdoc->setRenderHint(Poppler::Document::TextAntialiasing, true); while (pdoc->isLocked()) { KPasswordDialog dlg(0); dlg.setPrompt(i18n("A password is required to read that pdf")); dlg.setWindowTitle(i18n("A password is required to read that pdf")); if (dlg.exec() != QDialog::Accepted) { dbgFile << "Password canceled"; - return KisImportExportFilter::StorageCreationError; + return ImportExportCodes::Cancelled; } else pdoc->unlock(dlg.password().toLocal8Bit(), dlg.password().toLocal8Bit()); } KoDialog* kdb = new KoDialog(0); kdb->setCaption(i18n("PDF Import Options")); kdb->setModal(false); KisPDFImportWidget* wdg = new KisPDFImportWidget(pdoc, kdb); kdb->setMainWidget(wdg); QApplication::restoreOverrideCursor(); if (kdb->exec() == QDialog::Rejected) { delete pdoc; delete kdb; - return KisImportExportFilter::StorageCreationError; // FIXME Cancel doesn't exist :( + return ImportExportCodes::Cancelled; } // Create the krita image const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); int width = wdg->intWidth->value(); int height = wdg->intHeight->value(); KisImageSP image = new KisImage(document->createUndoStore(), width, height, cs, "built image"); image->setResolution(wdg->intResolution->value() / 72.0, wdg->intResolution->value() / 72.0); // create a layer QList pages = wdg->pages(); for (QList::const_iterator it = pages.constBegin(); it != pages.constEnd(); ++it) { KisPaintLayer* layer = new KisPaintLayer(image.data(), i18n("Page %1", *it + 1), quint8_MAX); Poppler::Page* page = pdoc->page(*it); QImage img = page->renderToImage(wdg->intResolution->value(), wdg->intResolution->value(), 0, 0, width, height); layer->paintDevice()->convertFromQImage(img, 0, 0, 0); delete page; image->addNode(layer, image->rootLayer(), 0); setProgress(qreal(*it + 1) * 100 / pages.count()); } document->setCurrentImage(image); delete pdoc; delete kdb; - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_pdf_import.moc" diff --git a/plugins/impex/pdf/kis_pdf_import.h b/plugins/impex/pdf/kis_pdf_import.h index df387515ff..76593a58b7 100644 --- a/plugins/impex/pdf/kis_pdf_import.h +++ b/plugins/impex/pdf/kis_pdf_import.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2006 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_PDF_IMPORT_H #define KIS_PDF_IMPORT_H #include #include class KisPDFImport : public KisImportExportFilter { Q_OBJECT public: KisPDFImport(QObject *parent, const QVariantList &); virtual ~KisPDFImport(); public: - virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0); + virtual KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0); }; #endif diff --git a/plugins/impex/pdf/tests/CMakeLists.txt b/plugins/impex/pdf/tests/CMakeLists.txt new file mode 100644 index 0000000000..19cb770163 --- /dev/null +++ b/plugins/impex/pdf/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisPdfTest.cpp + TEST_NAME KisPdfTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/pdf/tests/KisPdfTest.cpp similarity index 64% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/pdf/tests/KisPdfTest.cpp index 6ca66688f8..1f0dcec5ab 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/pdf/tests/KisPdfTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "kis_svg_test.h" +#include "KisPdfTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString PdfMimetype = "image/x-gimp-brush"; + + + +void KisPdfTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PdfMimetype); +} + + +void KisPdfTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), PdfMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisPdfTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), PdfMimetype); +} + + + +KISTEST_MAIN(KisPdfTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/pdf/tests/KisPdfTest.h similarity index 74% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/pdf/tests/KisPdfTest.h index 5c19b0cda6..a6d0203eed 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/pdf/tests/KisPdfTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _KIS_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_PDF_TEST_H_ +#define _KIS_PDF_TEST_H_ #include -class KisJpegTest : public QObject +class KisPdfTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_PDF_TEST_H_ + diff --git a/plugins/impex/pdf/tests/data/incorrectFormatFile.txt b/plugins/impex/pdf/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/pdf/tests/data/readonlyFile.txt b/plugins/impex/pdf/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/pdf/tests/data/writeonlyFile.txt b/plugins/impex/pdf/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/png/kis_png_export.cc b/plugins/impex/png/kis_png_export.cc index bd937658f2..d813603159 100644 --- a/plugins/impex/png/kis_png_export.cc +++ b/plugins/impex/png/kis_png_export.cc @@ -1,252 +1,247 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_png_export.h" #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include K_PLUGIN_FACTORY_WITH_JSON(KisPNGExportFactory, "krita_png_export.json", registerPlugin();) KisPNGExport::KisPNGExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPNGExport::~KisPNGExport() { } -KisImportExportFilter::ConversionStatus KisPNGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisPNGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KisImageSP image = document->savingImage(); KisPNGOptions options; options.alpha = configuration->getBool("alpha", true); options.interlace = configuration->getBool("interlaced", false); options.compression = configuration->getInt("compression", 3); options.tryToSaveAsIndexed = configuration->getBool("indexed", false); KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromQColor(Qt::white); options.transparencyFillColor = configuration->getColor("transparencyFillcolor", c).toQColor(); options.saveSRGBProfile = configuration->getBool("saveSRGBProfile", false); options.forceSRGB = configuration->getBool("forceSRGB", true); options.storeAuthor = configuration->getBool("storeAuthor", false); options.storeMetaData = configuration->getBool("storeMetaData", false); options.saveAsHDR = configuration->getBool("saveAsHDR", false); vKisAnnotationSP_it beginIt = image->beginAnnotations(); vKisAnnotationSP_it endIt = image->endAnnotations(); KisExifInfoVisitor eIV; eIV.visit(image->rootLayer().data()); KisMetaData::Store *eI = 0; if (eIV.metaDataCount() == 1) { eI = eIV.exifInfo(); } if (eI) { KisMetaData::Store* copy = new KisMetaData::Store(*eI); eI = copy; } KisPNGConverter pngConverter(document); - KisImageBuilder_Result res = pngConverter.buildFile(io, image->bounds(), image->xRes(), image->yRes(), image->projection(), beginIt, endIt, options, eI); - - if (res == KisImageBuilder_RESULT_OK) { - delete eI; - return KisImportExportFilter::OK; - } - + KisImportExportErrorCode res = pngConverter.buildFile(io, image->bounds(), image->xRes(), image->yRes(), image->projection(), beginIt, endIt, options, eI); delete eI; dbgFile << " Result =" << res; - return KisImportExportFilter::InternalError; + return res; } KisPropertiesConfigurationSP KisPNGExport::defaultConfiguration(const QByteArray &, const QByteArray &) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("alpha", true); cfg->setProperty("indexed", false); cfg->setProperty("compression", 3); cfg->setProperty("interlaced", false); KoColor fill_color(KoColorSpaceRegistry::instance()->rgb8()); fill_color = KoColor(); fill_color.fromQColor(Qt::white); QVariant v; v.setValue(fill_color); cfg->setProperty("transparencyFillcolor", v); cfg->setProperty("saveSRGBProfile", false); cfg->setProperty("forceSRGB", true); cfg->setProperty("saveAsHDR", false); cfg->setProperty("storeMetaData", false); cfg->setProperty("storeAuthor", false); return cfg; } KisConfigWidget *KisPNGExport::createConfigurationWidget(QWidget *parent, const QByteArray &, const QByteArray &) const { return new KisWdgOptionsPNG(parent); } void KisPNGExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "PNG"); } KisWdgOptionsPNG::KisWdgOptionsPNG(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); connect(chkSaveAsHDR, SIGNAL(toggled(bool)), this, SLOT(slotUseHDRChanged(bool))); } void KisWdgOptionsPNG::setConfiguration(const KisPropertiesConfigurationSP cfg) { // the export manager should have prepared some info for us! KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::ImageContainsTransparencyTag)); KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::ColorModelIDTag)); KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::sRGBTag)); const bool isThereAlpha = cfg->getBool(KisImportExportFilter::ImageContainsTransparencyTag); alpha->setChecked(cfg->getBool("alpha", isThereAlpha)); bnTransparencyFillColor->setEnabled(!alpha->isChecked()); if (cfg->getString(KisImportExportFilter::ColorModelIDTag) == RGBAColorModelID.id()) { tryToSaveAsIndexed->setVisible(true); if (alpha->isChecked()) { tryToSaveAsIndexed->setChecked(false); } else { tryToSaveAsIndexed->setChecked(cfg->getBool("indexed", false)); } } else { tryToSaveAsIndexed->setVisible(false); } interlacing->setChecked(cfg->getBool("interlaced", false)); compressionLevel->setValue(cfg->getInt("compression", 3)); compressionLevel->setRange(1, 9, 0); tryToSaveAsIndexed->setVisible(!isThereAlpha); //const bool sRGB = cfg->getBool(KisImportExportFilter::sRGBTag, false); //chkSRGB->setEnabled(sRGB); chkSRGB->setChecked(cfg->getBool("saveSRGBProfile", true)); //chkForceSRGB->setEnabled(!sRGB); chkForceSRGB->setChecked(cfg->getBool("forceSRGB", false)); chkSaveAsHDR->setChecked(cfg->getBool("saveAsHDR", false)); slotUseHDRChanged(chkSaveAsHDR->isChecked()); chkAuthor->setChecked(cfg->getBool("storeAuthor", false)); chkMetaData->setChecked(cfg->getBool("storeMetaData", false)); KoColor background(KoColorSpaceRegistry::instance()->rgb8()); background.fromQColor(Qt::white); bnTransparencyFillColor->setDefaultColor(background); bnTransparencyFillColor->setColor(cfg->getColor("transparencyFillcolor", background)); } KisPropertiesConfigurationSP KisWdgOptionsPNG::configuration() const { KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); bool alpha = this->alpha->isChecked(); bool interlace = interlacing->isChecked(); int compression = (int)compressionLevel->value(); bool saveAsHDR = chkSaveAsHDR->isChecked(); bool tryToSaveAsIndexed = !saveAsHDR && this->tryToSaveAsIndexed->isChecked(); bool saveSRGB = !saveAsHDR && chkSRGB->isChecked(); bool forceSRGB = !saveAsHDR && chkForceSRGB->isChecked(); bool storeAuthor = chkAuthor->isChecked(); bool storeMetaData = chkMetaData->isChecked(); QVariant transparencyFillcolor; transparencyFillcolor.setValue(bnTransparencyFillColor->color()); cfg->setProperty("alpha", alpha); cfg->setProperty("indexed", tryToSaveAsIndexed); cfg->setProperty("compression", compression); cfg->setProperty("interlaced", interlace); cfg->setProperty("transparencyFillcolor", transparencyFillcolor); cfg->setProperty("saveAsHDR", saveAsHDR); cfg->setProperty("saveSRGBProfile", saveSRGB); cfg->setProperty("forceSRGB", forceSRGB); cfg->setProperty("storeAuthor", storeAuthor); cfg->setProperty("storeMetaData", storeMetaData); return cfg; } void KisWdgOptionsPNG::on_alpha_toggled(bool checked) { bnTransparencyFillColor->setEnabled(!checked); } void KisWdgOptionsPNG::slotUseHDRChanged(bool value) { tryToSaveAsIndexed->setDisabled(value); chkForceSRGB->setDisabled(value); chkSRGB->setDisabled(value); } #include "kis_png_export.moc" diff --git a/plugins/impex/png/kis_png_export.h b/plugins/impex/png/kis_png_export.h index 18c91e2c78..3be3e51478 100644 --- a/plugins/impex/png/kis_png_export.h +++ b/plugins/impex/png/kis_png_export.h @@ -1,59 +1,59 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_PNG_EXPORT_H_ #define _KIS_PNG_EXPORT_H_ #include "ui_kis_wdg_options_png.h" #include #include class KisWdgOptionsPNG : public KisConfigWidget, public Ui::KisWdgOptionsPNG { Q_OBJECT public: KisWdgOptionsPNG(QWidget *parent); void setConfiguration(const KisPropertiesConfigurationSP config) override; KisPropertiesConfigurationSP configuration() const override; private Q_SLOTS: void on_alpha_toggled(bool checked); void slotUseHDRChanged(bool value); }; class KisPNGExport : public KisImportExportFilter { Q_OBJECT public: KisPNGExport(QObject *parent, const QVariantList &); ~KisPNGExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/png/kis_png_import.cc b/plugins/impex/png/kis_png_import.cc index b45fcad441..29a82cea2d 100644 --- a/plugins/impex/png/kis_png_import.cc +++ b/plugins/impex/png/kis_png_import.cc @@ -1,79 +1,56 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_png_import.h" #include #include #include #include #include #include #include "kis_png_converter.h" K_PLUGIN_FACTORY_WITH_JSON(PNGImportFactory, "krita_png_import.json", registerPlugin();) KisPNGImport::KisPNGImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPNGImport::~KisPNGImport() { } -KisImportExportFilter::ConversionStatus KisPNGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisPNGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisPNGConverter ib(document, batchMode()); - - switch (ib.buildImage(io)) { - case KisImageBuilder_RESULT_UNSUPPORTED: - return KisImportExportFilter::NotImplemented; - break; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - break; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_EXIST: - case KisImageBuilder_RESULT_NOT_LOCAL: - return KisImportExportFilter::FileNotFound; - break; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - break; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InternalError; - break; - case KisImageBuilder_RESULT_OK: - document -> setCurrentImage(ib.image()); - return KisImportExportFilter::OK; - break; - default: - break; + KisImportExportErrorCode res = ib.buildImage(io); + if (res.isOk()){ + document->setCurrentImage(ib.image()); } - return KisImportExportFilter::InternalError; + return res; } #include diff --git a/plugins/impex/png/kis_png_import.h b/plugins/impex/png/kis_png_import.h index 51d0ac7134..8b43397eb8 100644 --- a/plugins/impex/png/kis_png_import.h +++ b/plugins/impex/png/kis_png_import.h @@ -1,36 +1,36 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_PNG_IMPORT_H_ #define _KIS_PNG_IMPORT_H_ #include #include class KisPNGImport : public KisImportExportFilter { Q_OBJECT public: KisPNGImport(QObject *parent, const QVariantList &); ~KisPNGImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/png/tests/data/writeonlyFile.txt b/plugins/impex/png/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/png/tests/kis_png_test.cpp b/plugins/impex/png/tests/kis_png_test.cpp index 583ba20488..2259cb7822 100644 --- a/plugins/impex/png/tests/kis_png_test.cpp +++ b/plugins/impex/png/tests/kis_png_test.cpp @@ -1,155 +1,162 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_png_test.h" #include #include #include "filestest.h" #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString PngMimetype = "image/png"; + void KisPngTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); } +void KisPngTest::testWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PngMimetype); +} + void roudTripHdrImage(const KoColorSpace *savingColorSpace) { qDebug() << "Test saving" << savingColorSpace->id() << savingColorSpace->profile()->name(); const KoColorSpace * scRGBF32 = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p709G10Profile()); KoColor fillColor(scRGBF32); float *pixelPtr = reinterpret_cast(fillColor.data()); pixelPtr[0] = 2.7; pixelPtr[1] = 1.6; pixelPtr[2] = 0.8; pixelPtr[3] = 0.9; { QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP image = new KisImage(0, 3, 3, scRGBF32, "png test"); KisPaintLayerSP paintLayer0 = new KisPaintLayer(image, "paint0", OPACITY_OPAQUE_U8); paintLayer0->paintDevice()->fill(image->bounds(), fillColor); image->addNode(paintLayer0, image->root()); // convert image color space before saving image->convertImageColorSpace(savingColorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->waitForDone(); KisImportExportManager manager(doc.data()); doc->setFileBatchMode(true); doc->setCurrentImage(image); KisPropertiesConfigurationSP exportConfiguration = new KisPropertiesConfiguration(); exportConfiguration->setProperty("saveAsHDR", true); exportConfiguration->setProperty("saveSRGBProfile", false); exportConfiguration->setProperty("forceSRGB", false); doc->exportDocumentSync(QUrl::fromLocalFile("test.png"), "image/png", exportConfiguration); } { QScopedPointer doc(KisPart::instance()->createDocument()); KisImportExportManager manager(doc.data()); doc->setFileBatchMode(true); - KisImportExportFilter::ConversionStatus loadingStatus = + KisImportExportErrorCode loadingStatus = manager.importDocument("test.png", QString()); - QCOMPARE(loadingStatus, KisImportExportFilter::OK); + QVERIFY(loadingStatus.isOk()); KisImageSP image = doc->image(); image->initialRefreshGraph(); KoColor resultColor; // qDebug() << ppVar(image->colorSpace()) << image->colorSpace()->profile()->name(); // image->projection()->pixel(1, 1, &resultColor); // qDebug() << ppVar(resultColor); image->convertImageColorSpace(scRGBF32, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->waitForDone(); image->projection()->pixel(1, 1, &resultColor); // qDebug() << ppVar(resultColor); const float tolerance = savingColorSpace->colorDepthId() == Integer8BitsColorDepthID ? 0.02 : 0.01; bool resultIsValid = true; float *resultPtr = reinterpret_cast(resultColor.data()); for (int i = 0; i < 4; i++) { resultIsValid &= qAbs(resultPtr[i] - pixelPtr[i]) < tolerance; } if (!resultIsValid) { qDebug() << ppVar(fillColor) << ppVar(resultColor); } QVERIFY(resultIsValid); } } void KisPngTest::testSaveHDR() { QVector colorDepthIds; colorDepthIds << Float16BitsColorDepthID; colorDepthIds << Float32BitsColorDepthID; QVector profiles; profiles << KoColorSpaceRegistry::instance()->p709G10Profile(); profiles << KoColorSpaceRegistry::instance()->p2020G10Profile(); profiles << KoColorSpaceRegistry::instance()->p2020PQProfile(); Q_FOREACH(const KoID &depth, colorDepthIds) { Q_FOREACH(const KoColorProfile *profile, profiles) { roudTripHdrImage( KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), depth.id(), profile)); } } roudTripHdrImage( KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020PQProfile())); roudTripHdrImage( KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020PQProfile())); } KISTEST_MAIN(KisPngTest) diff --git a/plugins/impex/png/tests/kis_png_test.h b/plugins/impex/png/tests/kis_png_test.h index e312f85756..0c2374b4c6 100644 --- a/plugins/impex/png/tests/kis_png_test.h +++ b/plugins/impex/png/tests/kis_png_test.h @@ -1,32 +1,33 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_PNG_TEST_H_ #define _KIS_PNG_TEST_H_ #include class KisPngTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + void testWriteonly(); void testSaveHDR(); }; #endif diff --git a/plugins/impex/ppm/kis_ppm_export.cpp b/plugins/impex/ppm/kis_ppm_export.cpp index b93eb05a09..9dcb24478a 100644 --- a/plugins/impex/ppm/kis_ppm_export.cpp +++ b/plugins/impex/ppm/kis_ppm_export.cpp @@ -1,283 +1,316 @@ /* * Copyright (c) 2009 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_ppm_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include K_PLUGIN_FACTORY_WITH_JSON(KisPPMExportFactory, "krita_ppm_export.json", registerPlugin();) KisPPMExport::KisPPMExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPPMExport::~KisPPMExport() { } class KisPPMFlow { public: KisPPMFlow() { } virtual ~KisPPMFlow() { } virtual void writeBool(quint8 v) = 0; virtual void writeBool(quint16 v) = 0; virtual void writeNumber(quint8 v) = 0; virtual void writeNumber(quint16 v) = 0; virtual void flush() = 0; private: }; class KisPPMAsciiFlow : public KisPPMFlow { public: KisPPMAsciiFlow(QIODevice* device) : m_device(device) { } ~KisPPMAsciiFlow() override { } void writeBool(quint8 v) override { if (v > 127) { m_device->write("1 "); } else { m_device->write("0 "); } } void writeBool(quint16 v) override { writeBool(quint8(v >> 8)); } void writeNumber(quint8 v) override { m_device->write(QByteArray::number(v)); m_device->write(" "); } void writeNumber(quint16 v) override { m_device->write(QByteArray::number(v)); m_device->write(" "); } void flush() override { } private: QIODevice* m_device; }; class KisPPMBinaryFlow : public KisPPMFlow { public: KisPPMBinaryFlow(QIODevice* device) : m_device(device), m_pos(0), m_current(0) { } ~KisPPMBinaryFlow() override { } void writeBool(quint8 v) override { m_current = m_current << 1; m_current |= (v > 127); ++m_pos; if (m_pos >= 8) { m_current = 0; m_pos = 0; flush(); } } void writeBool(quint16 v) override { writeBool(quint8(v >> 8)); } void writeNumber(quint8 v) override { m_device->write((char*)&v, 1); } void writeNumber(quint16 v) override { quint16 vo = qToBigEndian(v); m_device->write((char*)&vo, 2); } void flush() override { m_device->write((char*)&m_current, 1); } private: QIODevice* m_device; int m_pos; quint8 m_current; }; -KisImportExportFilter::ConversionStatus KisPPMExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisPPMExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { bool rgb = (mimeType() == "image/x-portable-pixmap"); bool binary = (configuration->getInt("type") == 0); bool bitmap = (mimeType() == "image/x-portable-bitmap"); KisImageSP image = document->savingImage(); KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); // Test color space if (((rgb && (pd->colorSpace()->id() != "RGBA" && pd->colorSpace()->id() != "RGBA16")) || (!rgb && (pd->colorSpace()->id() != "GRAYA" && pd->colorSpace()->id() != "GRAYA16" && pd->colorSpace()->id() != "GRAYAU16")))) { if (rgb) { pd->convertTo(KoColorSpaceRegistry::instance()->rgb8(0), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } else { pd->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), 0), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } } bool is16bit = pd->colorSpace()->id() == "RGBA16" || pd->colorSpace()->id() == "GRAYAU16"; // Write the magic + QString toWrite = ""; + int written = 0; if (rgb) { - if (binary) io->write("P6"); - else io->write("P3"); + if (binary) { + toWrite = "P6"; + } + else { + toWrite = "P3"; + } } else if (bitmap) { - if (binary) io->write("P4"); - else io->write("P1"); + if (binary) { + toWrite = "P4"; + } + else { + toWrite = "P1"; + } } else { - if (binary) io->write("P5"); - else io->write("P2"); + if (binary) { + toWrite = "P5"; + } + else { + toWrite = "P2"; + } } - io->write("\n"); + written = io->write(toWrite.toUtf8()); + if (written != toWrite.toUtf8().length()) { + return ImportExportCodes::ErrorWhileWriting; + } + + written = io->write("\n"); // Write the header - io->write(QByteArray::number(image->width())); - io->write(" "); - io->write(QByteArray::number(image->height())); + QByteArray width = QByteArray::number(image->width()); + QByteArray height = QByteArray::number(image->height()); + + written += io->write(width); + written += io->write(" "); + written += io->write(height); + if (written != QString(" ").length() + QString("\n").length() + width.length() + height.length()) { + return ImportExportCodes::ErrorWhileWriting; + } if (!bitmap) { - if (is16bit) io->write(" 65535\n"); - else io->write(" 255\n"); + if (is16bit) { + toWrite = " 65535\n"; + } + else { + toWrite = " 255\n"; + } } else { - io->write("\n"); + toWrite = "\n"; + } + written = io->write(toWrite.toUtf8()); + if (written != toWrite.toUtf8().length()) { + return ImportExportCodes::ErrorWhileWriting; } // Write the data KisPPMFlow* flow = 0; if (binary) flow = new KisPPMBinaryFlow(io); else flow = new KisPPMAsciiFlow(io); for (int y = 0; y < image->height(); ++y) { KisHLineIteratorSP it = pd->createHLineIteratorNG(0, y, image->width()); if (is16bit) { if (rgb) { do { flow->writeNumber(KoBgrU16Traits::red(it->rawData())); flow->writeNumber(KoBgrU16Traits::green(it->rawData())); flow->writeNumber(KoBgrU16Traits::blue(it->rawData())); } while (it->nextPixel()); } else if (bitmap) { do { flow->writeBool(*reinterpret_cast(it->rawData())); } while (it->nextPixel()); } else { do { flow->writeNumber(*reinterpret_cast(it->rawData())); } while (it->nextPixel()); } } else { if (rgb) { do { flow->writeNumber(KoBgrTraits::red(it->rawData())); flow->writeNumber(KoBgrTraits::green(it->rawData())); flow->writeNumber(KoBgrTraits::blue(it->rawData())); } while (it->nextPixel()); } else if (bitmap) { do { flow->writeBool(*reinterpret_cast(it->rawData())); } while (it->nextPixel()); } else { do { flow->writeNumber(*reinterpret_cast(it->rawData())); } while (it->nextPixel()); } } } if (bitmap) { flow->flush(); } delete flow; - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } KisPropertiesConfigurationSP KisPPMExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("type", 0); return cfg; } KisConfigWidget *KisPPMExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisWdgOptionsPPM(parent); } void KisWdgOptionsPPM::setConfiguration(const KisPropertiesConfigurationSP cfg) { cmbType->setCurrentIndex(cfg->getInt("type", 0)); } KisPropertiesConfigurationSP KisWdgOptionsPPM::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("type", cmbType->currentIndex()); return cfg; } void KisPPMExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + RGBAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + RGBAColorModelID.id() + "/" + Integer16BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + GrayAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + GrayAColorModelID.id() + "/" + Integer16BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "PPM"); } #include "kis_ppm_export.moc" diff --git a/plugins/impex/ppm/kis_ppm_export.h b/plugins/impex/ppm/kis_ppm_export.h index 7689305acb..53f15c5e70 100644 --- a/plugins/impex/ppm/kis_ppm_export.h +++ b/plugins/impex/ppm/kis_ppm_export.h @@ -1,58 +1,58 @@ /* * Copyright (c) 2009 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_PPM_EXPORT_H_ #define _KIS_PPM_EXPORT_H_ #include #include #include #include "ui_kis_wdg_options_ppm.h" class KisWdgOptionsPPM : public KisConfigWidget, public Ui::WdgOptionsPPM { Q_OBJECT public: KisWdgOptionsPPM(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); } void setConfiguration(const KisPropertiesConfigurationSP cfg) override; KisPropertiesConfigurationSP configuration() const override; }; class KisPPMExport : public KisImportExportFilter { Q_OBJECT public: KisPPMExport(QObject *parent, const QVariantList &); ~KisPPMExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/ppm/kis_ppm_import.cpp b/plugins/impex/ppm/kis_ppm_import.cpp index 32eae9206d..ee31b591d3 100644 --- a/plugins/impex/ppm/kis_ppm_import.cpp +++ b/plugins/impex/ppm/kis_ppm_import.cpp @@ -1,314 +1,320 @@ /* * Copyright (c) 2009 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_ppm_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" K_PLUGIN_FACTORY_WITH_JSON(PPMImportFactory, "krita_ppm_import.json", registerPlugin();) KisPPMImport::KisPPMImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPPMImport::~KisPPMImport() { } int readNumber(QIODevice* device) { char c; int val = 0; while (true) { if (!device->getChar(&c)) break; // End of the file if (isdigit(c)) { val = 10 * val + c - '0'; } else if (c == '#') { device->readLine(); break; } else if (isspace((uchar) c)) { break; } } return val; } class KisPpmFlow { public: KisPpmFlow() { } virtual ~KisPpmFlow() { } virtual void nextRow() = 0; virtual bool valid() = 0; virtual bool nextUint1() = 0; virtual quint8 nextUint8() = 0; virtual quint16 nextUint16() = 0; }; class KisAsciiPpmFlow : public KisPpmFlow { public: KisAsciiPpmFlow(QIODevice* device) : m_device(device) { } ~KisAsciiPpmFlow() override { } void nextRow() override { } bool valid() override { return !m_device->atEnd(); } bool nextUint1() override { return readNumber(m_device) == 1; } quint8 nextUint8() override { return readNumber(m_device); } quint16 nextUint16() override { return readNumber(m_device); } private: QIODevice* m_device; }; class KisBinaryPpmFlow : public KisPpmFlow { public: KisBinaryPpmFlow(QIODevice* device, int lineWidth) : m_pos(0), m_device(device), m_lineWidth(lineWidth) { } ~KisBinaryPpmFlow() override { } void nextRow() override { m_array = m_device->read(m_lineWidth); m_ptr = m_array.data(); } bool valid() override { return m_array.size() == m_lineWidth; } bool nextUint1() override { if (m_pos == 0) { m_current = nextUint8(); m_pos = 8; } bool v = (m_current & 1) == 1; --m_pos; m_current = m_current >> 1; return v; } quint8 nextUint8() override { quint8 v = *reinterpret_cast(m_ptr); m_ptr += 1; return v; } quint16 nextUint16() override { quint16 v = *reinterpret_cast(m_ptr); m_ptr += 2; return qFromBigEndian(v); } private: int m_pos; quint8 m_current; char* m_ptr; QIODevice* m_device; QByteArray m_array; int m_lineWidth; }; -KisImportExportFilter::ConversionStatus KisPPMImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisPPMImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { QByteArray array = io->read(2); - if (array.size() < 2) return KisImportExportFilter::CreationError; + if (array.size() < 2) { + return ImportExportCodes::FileFormatIncorrect; + } // Read the type of the ppm file enum { Puk, P1, P2, P3, P4, P5, P6 } fileType = Puk; // Puk => unknown int channels = -1; bool isAscii = false; if (array == "P1") { fileType = P1; isAscii = true; channels = 0; } else if (array == "P2") { fileType = P2; channels = 1; isAscii = true; } else if (array == "P3") { fileType = P3; channels = 3; isAscii = true; } else if (array == "P4") { fileType = P4; channels = 0; } else if (array == "P5") { // PGM fileType = P5; channels = 1; } else if (array == "P6") { // PPM fileType = P6; channels = 3; } Q_ASSERT(channels != -1); char c; io->getChar(&c); - if (!isspace(c)) return KisImportExportFilter::CreationError; // Invalid file, it should have a separator now + if (!isspace(c)) { + return ImportExportCodes::FileFormatIncorrect;; // Invalid file, it should have a separator now + } while (io->peek(1) == "#") { io->readLine(); } // Read width int width = readNumber(io); int height = readNumber(io); int maxval = 1; if (fileType != P1 && fileType != P4) { maxval = readNumber(io); } dbgFile << "Width = " << width << " height = " << height << "maxval = " << maxval; // Select the colorspace depending on the maximum value int pixelsize = -1; const KoColorSpace* colorSpace = 0; const KoColorProfile *profile = 0; QString colorSpaceId; QString bitDepthId; if (maxval <= 255) { bitDepthId = Integer8BitsColorDepthID.id(); if (channels == 1 || channels == 0) { pixelsize = 1; colorSpaceId = GrayAColorModelID.id(); } else { pixelsize = 3; colorSpaceId = RGBAColorModelID.id(); } } else if (maxval <= 65535) { bitDepthId = Integer16BitsColorDepthID.id(); if (channels == 1 || channels == 0) { pixelsize = 2; colorSpaceId = GrayAColorModelID.id(); } else { pixelsize = 6; colorSpaceId = RGBAColorModelID.id(); } } else { dbgFile << "Unknown colorspace"; - return KisImportExportFilter::CreationError; + return ImportExportCodes::FormatColorSpaceUnsupported; } if (colorSpaceId == RGBAColorModelID.id()) { profile = KoColorSpaceRegistry::instance()->profileByName("sRGB-elle-V2-srgbtrc.icc"); } else if (colorSpaceId == GrayAColorModelID.id()) { profile = KoColorSpaceRegistry::instance()->profileByName("Gray-D50-elle-V2-srgbtrc.icc"); } colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId, bitDepthId, profile); KisImageSP image = new KisImage(document->createUndoStore(), width, height, colorSpace, "built image"); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); QScopedPointer ppmFlow; if (isAscii) { ppmFlow.reset(new KisAsciiPpmFlow(io)); } else { ppmFlow.reset(new KisBinaryPpmFlow(io, pixelsize * width)); } for (int v = 0; v < height; ++v) { KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, v, width); ppmFlow->nextRow(); - if (!ppmFlow->valid()) return KisImportExportFilter::CreationError; + if (!ppmFlow->valid()) { + return ImportExportCodes::FileFormatIncorrect; + } if (maxval <= 255) { if (channels == 3) { do { KoBgrTraits::setRed(it->rawData(), ppmFlow->nextUint8()); KoBgrTraits::setGreen(it->rawData(), ppmFlow->nextUint8()); KoBgrTraits::setBlue(it->rawData(), ppmFlow->nextUint8()); colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } while (it->nextPixel()); } else if (channels == 1) { do { *reinterpret_cast(it->rawData()) = ppmFlow->nextUint8(); colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } while (it->nextPixel()); } else if (channels == 0) { do { if (ppmFlow->nextUint1()) { *reinterpret_cast(it->rawData()) = 255; } else { *reinterpret_cast(it->rawData()) = 0; } colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } while (it->nextPixel()); } } else { if (channels == 3) { do { KoBgrU16Traits::setRed(it->rawData(), ppmFlow->nextUint16()); KoBgrU16Traits::setGreen(it->rawData(), ppmFlow->nextUint16()); KoBgrU16Traits::setBlue(it->rawData(), ppmFlow->nextUint16()); colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } while (it->nextPixel()); } else if (channels == 1) { do { *reinterpret_cast(it->rawData()) = ppmFlow->nextUint16(); colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } while (it->nextPixel()); } } } image->addNode(layer.data(), image->rootLayer().data()); document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_ppm_import.moc" diff --git a/plugins/impex/ppm/kis_ppm_import.h b/plugins/impex/ppm/kis_ppm_import.h index f3921fece6..07268bff36 100644 --- a/plugins/impex/ppm/kis_ppm_import.h +++ b/plugins/impex/ppm/kis_ppm_import.h @@ -1,40 +1,40 @@ /* * Copyright (c) 2009 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_PPM_IMPORT_H_ #define _KIS_PPM_IMPORT_H_ #include #include #include class KisDocument; class KisPPMImport : public KisImportExportFilter { Q_OBJECT public: KisPPMImport(QObject *parent, const QVariantList &); ~KisPPMImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/ppm/tests/data/incorrectFormatFile.txt b/plugins/impex/ppm/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/ppm/tests/data/readonlyFile.txt b/plugins/impex/ppm/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/ppm/tests/data/writeonlyFile.txt b/plugins/impex/ppm/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/ppm/tests/kis_ppm_test.cpp b/plugins/impex/ppm/tests/kis_ppm_test.cpp index 6aa3859d0a..fce6ee9d5c 100644 --- a/plugins/impex/ppm/tests/kis_ppm_test.cpp +++ b/plugins/impex/ppm/tests/kis_ppm_test.cpp @@ -1,39 +1,61 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_ppm_test.h" #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif + +const QString PPMMimetype = "image/x-portable-pixmap"; + void KisPPMTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); } + +void KisPPMTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PPMMimetype); +} + + +void KisPPMTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), PPMMimetype); +} + + +void KisPPMTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), PPMMimetype); +} + + KISTEST_MAIN(KisPPMTest) diff --git a/plugins/impex/ppm/tests/kis_ppm_test.h b/plugins/impex/ppm/tests/kis_ppm_test.h index 36e9d19953..827856e793 100644 --- a/plugins/impex/ppm/tests/kis_ppm_test.h +++ b/plugins/impex/ppm/tests/kis_ppm_test.h @@ -1,31 +1,35 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_PPM_TEST_H_ #define _KIS_PPM_TEST_H_ #include class KisPPMTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; #endif diff --git a/plugins/impex/psd/psd_export.cc b/plugins/impex/psd/psd_export.cc index 05df481795..ca04ebeb8c 100644 --- a/plugins/impex/psd/psd_export.cc +++ b/plugins/impex/psd/psd_export.cc @@ -1,102 +1,91 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "psd_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "psd_saver.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_psd_export.json", registerPlugin();) psdExport::psdExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } psdExport::~psdExport() { } -KisImportExportFilter::ConversionStatus psdExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode psdExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { PSDSaver psdSaver(document); - KisImageBuilder_Result res; - - if ((res = psdSaver.buildFile(io)) == KisImageBuilder_RESULT_OK) { - dbgFile <<"success !"; - return KisImportExportFilter::OK; - } - else if (res == KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE) { - document->setErrorMessage(i18n("Could not convert this colorspace to something Krita can save.")); - return KisImportExportFilter::WrongFormat; - } - dbgFile <<" Result =" << res; - return KisImportExportFilter::InternalError; + return psdSaver.buildFile(io); } void psdExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("PSDLayerStyleCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisTransparencyMask")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelHomogenousCheck")->create(KisExportCheckBase::UNSUPPORTED, i18nc("image conversion warning", "Your image contains one or more layers with a color model that is different from the image."))); ImageSizeCheckFactory *factory = dynamic_cast(KisExportCheckRegistry::instance()->get("ImageSizeCheck")); if (factory) { addCapability(factory->create(30000, 30000, KisExportCheckBase::SUPPORTED)); } QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) // << QPair(RGBAColorModelID, Float16BitsColorDepthID) // << QPair(RGBAColorModelID, Float32BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID) << QPair(CMYKAColorModelID, Integer8BitsColorDepthID) << QPair(CMYKAColorModelID, Integer16BitsColorDepthID) << QPair(LABAColorModelID, Integer8BitsColorDepthID) << QPair(LABAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "PSD"); } #include diff --git a/plugins/impex/psd/psd_export.h b/plugins/impex/psd/psd_export.h index 0d66e19571..55d79b4d70 100644 --- a/plugins/impex/psd/psd_export.h +++ b/plugins/impex/psd/psd_export.h @@ -1,35 +1,35 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _PSD_EXPORT_H_ #define _PSD_EXPORT_H_ #include #include class psdExport : public KisImportExportFilter { Q_OBJECT public: psdExport(QObject *parent, const QVariantList &); ~psdExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/psd/psd_import.cc b/plugins/impex/psd/psd_import.cc index 0d8722f207..a73917e1e6 100644 --- a/plugins/impex/psd/psd_import.cc +++ b/plugins/impex/psd/psd_import.cc @@ -1,72 +1,49 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "psd_import.h" #include #include #include #include #include "psd_loader.h" K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_psd_import.json", registerPlugin();) psdImport::psdImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } psdImport::~psdImport() { } -KisImportExportFilter::ConversionStatus psdImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode psdImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { - - PSDLoader ib(document); - - KisImageBuilder_Result result = ib.buildImage(io); - - switch (result) { - case KisImageBuilder_RESULT_UNSUPPORTED: - return KisImportExportFilter::NotImplemented; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_EXIST: - case KisImageBuilder_RESULT_NOT_LOCAL: - qDebug() << "ib returned KisImageBuilder_RESULT_NOT_LOCAL"; - return KisImportExportFilter::FileNotFound; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InternalError; - case KisImageBuilder_RESULT_OK: - document -> setCurrentImage( ib.image()); - return KisImportExportFilter::OK; - default: - return KisImportExportFilter::StorageCreationError; - //dbgFile << "Result was: " << result; + KisImportExportErrorCode result = ib.buildImage(io); + if (result.isOk()) { + document->setCurrentImage(ib.image()); } - return KisImportExportFilter::InternalError; + return result; } #include diff --git a/plugins/impex/psd/psd_import.h b/plugins/impex/psd/psd_import.h index a522c7d5a0..606d7563ab 100644 --- a/plugins/impex/psd/psd_import.h +++ b/plugins/impex/psd/psd_import.h @@ -1,34 +1,34 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PSD_IMPORT_H_ #define PSD_IMPORT_H_ #include #include class psdImport : public KisImportExportFilter { Q_OBJECT public: psdImport(QObject *parent, const QVariantList &); ~psdImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/psd/psd_loader.cpp b/plugins/impex/psd/psd_loader.cpp index 0b99d9d1e6..f68e47a503 100644 --- a/plugins/impex/psd/psd_loader.cpp +++ b/plugins/impex/psd/psd_loader.cpp @@ -1,380 +1,380 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "psd_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "psd.h" #include "psd_header.h" #include "psd_colormode_block.h" #include "psd_utils.h" #include "psd_resource_section.h" #include "psd_layer_section.h" #include "psd_resource_block.h" #include "psd_image_data.h" PSDLoader::PSDLoader(KisDocument *doc) : m_image(0) , m_doc(doc) , m_stop(false) { } PSDLoader::~PSDLoader() { } -KisImageBuilder_Result PSDLoader::decode(QIODevice *io) +KisImportExportErrorCode PSDLoader::decode(QIODevice *io) { // open the file dbgFile << "pos:" << io->pos(); PSDHeader header; - if (!header.read( io)) { + if (!header.read(io)) { dbgFile << "failed reading header: " << header.error; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } dbgFile << header; dbgFile << "Read header. pos:" << io->pos(); PSDColorModeBlock colorModeBlock(header.colormode); if (!colorModeBlock.read(io)) { dbgFile << "failed reading colormode block: " << colorModeBlock.error; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } dbgFile << "Read color mode block. pos:" << io->pos(); PSDImageResourceSection resourceSection; if (!resourceSection.read(io)) { dbgFile << "failed image reading resource section: " << resourceSection.error; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } dbgFile << "Read image resource section. pos:" << io->pos(); PSDLayerMaskSection layerSection(header); if (!layerSection.read(io)) { dbgFile << "failed reading layer/mask section: " << layerSection.error; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } dbgFile << "Read layer/mask section. " << layerSection.nLayers << "layers. pos:" << io->pos(); // Done reading, except possibly for the image data block, which is only relevant if there // are no layers. // Get the right colorspace QPair colorSpaceId = psd_colormode_to_colormodelid(header.colormode, header.channelDepth); if (colorSpaceId.first.isNull()) { dbgFile << "Unsupported colorspace" << header.colormode << header.channelDepth; - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } // Get the icc profile from the image resource section const KoColorProfile* profile = 0; if (resourceSection.resources.contains(PSDImageResourceSection::ICC_PROFILE)) { ICC_PROFILE_1039 *iccProfileData = dynamic_cast(resourceSection.resources[PSDImageResourceSection::ICC_PROFILE]->resource); if (iccProfileData ) { profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceId.first, colorSpaceId.second, iccProfileData->icc); dbgFile << "Loaded ICC profile" << profile->name(); delete resourceSection.resources.take(PSDImageResourceSection::ICC_PROFILE); } } // Create the colorspace const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile); if (!cs) { - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } // Creating the KisImage QFile *file = dynamic_cast(io); QString name = file ? file->fileName() : "Imported"; m_image = new KisImage(m_doc->createUndoStore(), header.width, header.height, cs, name); Q_CHECK_PTR(m_image); m_image->lock(); // set the correct resolution if (resourceSection.resources.contains(PSDImageResourceSection::RESN_INFO)) { RESN_INFO_1005 *resInfo = dynamic_cast(resourceSection.resources[PSDImageResourceSection::RESN_INFO]->resource); if (resInfo) { // check resolution size is not zero if (resInfo->hRes * resInfo->vRes > 0) m_image->setResolution(POINT_TO_INCH(resInfo->hRes), POINT_TO_INCH(resInfo->vRes)); // let's skip the unit for now; we can only set that on the KisDocument, and krita doesn't use it. delete resourceSection.resources.take(PSDImageResourceSection::RESN_INFO); } } // Preserve all the annotations Q_FOREACH (PSDResourceBlock *resourceBlock, resourceSection.resources.values()) { m_image->addAnnotation(resourceBlock); } // Preserve the duotone colormode block for saving back to psd if (header.colormode == DuoTone) { KisAnnotationSP annotation = new KisAnnotation("DuotoneColormodeBlock", i18n("Duotone Colormode Block"), colorModeBlock.data); m_image->addAnnotation(annotation); } // Read the projection into our single layer. Since we only read the projection when // we have just one layer, we don't need to later on apply the alpha channel of the // first layer to the projection if the number of layers is negative/ // See http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000. if (layerSection.nLayers == 0) { dbgFile << "Position" << io->pos() << "Going to read the projection into the first layer, which Photoshop calls 'Background'"; KisPaintLayerSP layer = new KisPaintLayer(m_image, i18n("Background"), OPACITY_OPAQUE_U8); PSDImageData imageData(&header); imageData.read(io, layer->paintDevice()); m_image->addNode(layer, m_image->rootLayer()); // Only one layer, the background layer, so we're done. m_image->unlock(); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } // More than one layer, so now construct the Krita image from the info we read. QStack groupStack; groupStack.push(m_image->rootLayer()); /** * PSD has a weird "optimization": if a group layer has only one * child layer, it omits it's 'psd_bounding_divider' section. So * fi you ever see an unbalanced layers group in PSD, most * probably, it is just a single layered group. */ KisNodeSP lastAddedLayer; typedef QPair LayerStyleMapping; QVector allStylesXml; // read the channels for the various layers for(int i = 0; i < layerSection.nLayers; ++i) { PSDLayerRecord* layerRecord = layerSection.layers.at(i); dbgFile << "Going to read channels for layer" << i << layerRecord->layerName; KisLayerSP newLayer; if (layerRecord->infoBlocks.keys.contains("lsct") && layerRecord->infoBlocks.sectionDividerType != psd_other) { if (layerRecord->infoBlocks.sectionDividerType == psd_bounding_divider && !groupStack.isEmpty()) { KisGroupLayerSP groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8); m_image->addNode(groupLayer, groupStack.top()); groupStack.push(groupLayer); newLayer = groupLayer; } else if ((layerRecord->infoBlocks.sectionDividerType == psd_open_folder || layerRecord->infoBlocks.sectionDividerType == psd_closed_folder) && (groupStack.size() > 1 || (lastAddedLayer && !groupStack.isEmpty()))) { KisGroupLayerSP groupLayer; if (groupStack.size() <= 1) { groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8); m_image->addNode(groupLayer, groupStack.top()); m_image->moveNode(lastAddedLayer, groupLayer, KisNodeSP()); } else { groupLayer = groupStack.pop(); } const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml; if (!styleXml.isNull()) { allStylesXml << LayerStyleMapping(styleXml, groupLayer); } groupLayer->setName(layerRecord->layerName); groupLayer->setVisible(layerRecord->visible); QString compositeOp = psd_blendmode_to_composite_op(layerRecord->infoBlocks.sectionDividerBlendMode); // Krita doesn't support pass-through blend // mode. Instead it is just a property of a group // layer, so flip it if (compositeOp == COMPOSITE_PASS_THROUGH) { compositeOp = COMPOSITE_OVER; groupLayer->setPassThroughMode(true); } groupLayer->setCompositeOpId(compositeOp); newLayer = groupLayer; } else { /** * In some files saved by PS CS6 the group layer sections seem * to be unbalanced. I don't know why it happens because the * reporter didn't provide us an example file. So here we just * check if the new layer was created, and if not, skip the * initialization of masks. * * See bug: 357559 */ warnKrita << "WARNING: Provided PSD has unbalanced group " << "layer markers. Some masks and/or layers can " << "be lost while loading this file. Please " << "report a bug to Krita developers and attach " << "this file to the bugreport\n" << " " << ppVar(layerRecord->layerName) << "\n" << " " << ppVar(layerRecord->infoBlocks.sectionDividerType) << "\n" << " " << ppVar(groupStack.size()); continue; } } else { KisPaintLayerSP layer = new KisPaintLayer(m_image, layerRecord->layerName, layerRecord->opacity); layer->setCompositeOpId(psd_blendmode_to_composite_op(layerRecord->blendModeKey)); const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml; if (!styleXml.isNull()) { allStylesXml << LayerStyleMapping(styleXml, layer); } if (!layerRecord->readPixelData(io, layer->paintDevice())) { dbgFile << "failed reading channels for layer: " << layerRecord->layerName << layerRecord->error; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } if (!groupStack.isEmpty()) { m_image->addNode(layer, groupStack.top()); } else { m_image->addNode(layer, m_image->root()); } layer->setVisible(layerRecord->visible); newLayer = layer; } Q_FOREACH (ChannelInfo *channelInfo, layerRecord->channelInfoRecords) { if (channelInfo->channelId < -1) { KisTransparencyMaskSP mask = new KisTransparencyMask(); mask->setName(i18n("Transparency Mask")); mask->initSelection(newLayer); if (!layerRecord->readMask(io, mask->paintDevice(), channelInfo)) { dbgFile << "failed reading masks for layer: " << layerRecord->layerName << layerRecord->error; } m_image->addNode(mask, newLayer); } } lastAddedLayer = newLayer; } const QVector &embeddedPatterns = layerSection.globalInfoSection.embeddedPatterns; KisAslLayerStyleSerializer serializer; if (!embeddedPatterns.isEmpty()) { Q_FOREACH (const QDomDocument &doc, embeddedPatterns) { serializer.registerPSDPattern(doc); } } QVector allStylesForServer; if (!allStylesXml.isEmpty()) { Q_FOREACH (const LayerStyleMapping &mapping, allStylesXml) { serializer.readFromPSDXML(mapping.first); if (serializer.styles().size() == 1) { KisPSDLayerStyleSP layerStyle = serializer.styles().first(); KisLayerSP layer = mapping.second; layerStyle->setName(layer->name()); allStylesForServer << layerStyle; layer->setLayerStyle(layerStyle->clone()); } else { warnKrita << "WARNING: Couldn't read layer style!" << ppVar(serializer.styles()); } } } if (!allStylesForServer.isEmpty()) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded PSD Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_image->objectName())); KIS_SAFE_ASSERT_RECOVER_NOOP(!collection->valid()); collection->setLayerStyles(allStylesForServer); KIS_SAFE_ASSERT_RECOVER_NOOP(collection->valid()); KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); } m_image->unlock(); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -KisImageBuilder_Result PSDLoader::buildImage(QIODevice *io) +KisImportExportErrorCode PSDLoader::buildImage(QIODevice *io) { return decode(io); } KisImageSP PSDLoader::image() { return m_image; } void PSDLoader::cancel() { m_stop = true; } diff --git a/plugins/impex/psd/psd_loader.h b/plugins/impex/psd/psd_loader.h index 0618f803f5..0f096b20e2 100644 --- a/plugins/impex/psd/psd_loader.h +++ b/plugins/impex/psd/psd_loader.h @@ -1,59 +1,59 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _PSD_LOADER_H_ #define _PSD_LOADER_H_ #include #include #include #include "kis_types.h" -#include +#include class KisDocument; class PSDLoader : public QObject { Q_OBJECT public: PSDLoader(KisDocument *doc); ~PSDLoader() override; - KisImageBuilder_Result buildImage(QIODevice *io); + KisImportExportErrorCode buildImage(QIODevice *io); KisImageSP image(); public Q_SLOTS: virtual void cancel(); private: - KisImageBuilder_Result decode(QIODevice *io); + KisImportExportErrorCode decode(QIODevice *io); private: KisImageSP m_image; KisDocument *m_doc; bool m_stop; }; #endif diff --git a/plugins/impex/psd/psd_saver.cpp b/plugins/impex/psd/psd_saver.cpp index 3ed8d70b26..8ea25a8e84 100644 --- a/plugins/impex/psd/psd_saver.cpp +++ b/plugins/impex/psd/psd_saver.cpp @@ -1,250 +1,252 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "psd_saver.h" #include #include #include #include #include #include #include #include #include #include "kis_painter.h" #include #include #include #include #include #include #include "psd.h" #include "psd_header.h" #include "psd_colormode_block.h" #include "psd_utils.h" #include "psd_resource_section.h" #include "psd_layer_section.h" #include "psd_resource_block.h" #include "psd_image_data.h" +const int MAX_PSD_SIZE = 30000; + + QPair colormodelid_to_psd_colormode(const QString &colorSpaceId, const QString &colorDepthId) { psd_color_mode colorMode = COLORMODE_UNKNOWN; if (colorSpaceId == RGBAColorModelID.id()) { colorMode = RGB; } else if (colorSpaceId == CMYKAColorModelID.id()) { colorMode = CMYK; } else if (colorSpaceId == GrayAColorModelID.id()) { colorMode = Grayscale; } else if (colorSpaceId == LABAColorModelID.id()) { colorMode = Lab; } quint16 depth = 0; if (colorDepthId == Integer8BitsColorDepthID.id()) { depth = 8; } else if (colorDepthId == Integer16BitsColorDepthID.id()) { depth = 16; } else if (colorDepthId == Float16BitsColorDepthID.id()) { depth = 32; } else if (colorDepthId == Float32BitsColorDepthID.id()) { depth = 32; } return QPair(colorMode, depth); } PSDSaver::PSDSaver(KisDocument *doc) : m_image(doc->savingImage()) , m_doc(doc) , m_stop(false) { } PSDSaver::~PSDSaver() { } KisImageSP PSDSaver::image() { return m_image; } -KisImageBuilder_Result PSDSaver::buildFile(QIODevice *io) +KisImportExportErrorCode PSDSaver::buildFile(QIODevice *io) { - if (!m_image) { - return KisImageBuilder_RESULT_EMPTY; - } - if (m_image->width() > 30000 || m_image->height() > 30000) { - return KisImageBuilder_RESULT_FAILURE; + KIS_ASSERT_RECOVER_RETURN_VALUE(m_image, ImportExportCodes::InternalError); + + if (m_image->width() > MAX_PSD_SIZE || m_image->height() > MAX_PSD_SIZE) { + return ImportExportCodes::Failure; } const bool haveLayers = m_image->rootLayer()->childCount() > 1 || KisPainter::checkDeviceHasTransparency( m_image->rootLayer()->firstChild()->projection()); // HEADER PSDHeader header; header.signature = "8BPS"; header.version = 1; header.nChannels = haveLayers ? m_image->colorSpace()->channelCount() : m_image->colorSpace()->colorChannelCount(); header.width = m_image->width(); header.height = m_image->height(); QPair colordef = colormodelid_to_psd_colormode(m_image->colorSpace()->colorModelId().id(), m_image->colorSpace()->colorDepthId().id()); if (colordef.first == COLORMODE_UNKNOWN || colordef.second == 0 || colordef.second == 32) { m_image->convertImageColorSpace(KoColorSpaceRegistry::instance()->rgb16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); colordef = colormodelid_to_psd_colormode(m_image->colorSpace()->colorModelId().id(), m_image->colorSpace()->colorDepthId().id()); } header.colormode = colordef.first; header.channelDepth = colordef.second; dbgFile << "header" << header << io->pos(); if (!header.write(io)) { dbgFile << "Failed to write header. Error:" << header.error << io->pos(); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::ErrorWhileWriting; } // COLORMODE BlOCK PSDColorModeBlock colorModeBlock(header.colormode); // XXX: check for annotations that contain the duotone spec KisAnnotationSP annotation = m_image->annotation("DuotoneColormodeBlock"); if (annotation) { colorModeBlock.duotoneSpecification = annotation->annotation(); } dbgFile << "colormode block" << io->pos(); if (!colorModeBlock.write(io)) { dbgFile << "Failed to write colormode block. Error:" << colorModeBlock.error << io->pos(); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::ErrorWhileWriting; } // IMAGE RESOURCES SECTION PSDImageResourceSection resourceSection; vKisAnnotationSP_it it = m_image->beginAnnotations(); vKisAnnotationSP_it endIt = m_image->endAnnotations(); while (it != endIt) { KisAnnotationSP annotation = (*it); if (!annotation || annotation->type().isEmpty()) { dbgFile << "Warning: empty annotation"; it++; continue; } dbgFile << "Annotation:" << annotation->type() << annotation->description(); if (annotation->type().startsWith(QString("PSD Resource Block:"))) { // PSDResourceBlock *resourceBlock = dynamic_cast(annotation.data()); if (resourceBlock) { dbgFile << "Adding PSD Resource Block" << resourceBlock->identifier; resourceSection.resources[(PSDImageResourceSection::PSDResourceID)resourceBlock->identifier] = resourceBlock; } } it++; } // Add resolution block { RESN_INFO_1005 *resInfo = new RESN_INFO_1005; resInfo->hRes = INCH_TO_POINT(m_image->xRes()); resInfo->vRes = INCH_TO_POINT(m_image->yRes()); PSDResourceBlock *block = new PSDResourceBlock; block->identifier = PSDImageResourceSection::RESN_INFO; block->resource = resInfo; resourceSection.resources[PSDImageResourceSection::RESN_INFO] = block; } // Add icc block { ICC_PROFILE_1039 *profileInfo = new ICC_PROFILE_1039; profileInfo->icc = m_image->profile()->rawData(); PSDResourceBlock *block = new PSDResourceBlock; block->identifier = PSDImageResourceSection::ICC_PROFILE; block->resource = profileInfo; resourceSection.resources[PSDImageResourceSection::ICC_PROFILE] = block; } dbgFile << "resource section" << io->pos(); if (!resourceSection.write(io)) { dbgFile << "Failed to write resource section. Error:" << resourceSection.error << io->pos(); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::ErrorWhileWriting; } // LAYER AND MASK DATA // Only save layers and masks if there is more than one layer dbgFile << "m_image->rootLayer->childCount" << m_image->rootLayer()->childCount() << io->pos(); if (haveLayers) { PSDLayerMaskSection layerSection(header); layerSection.hasTransparency = true; if (!layerSection.write(io, m_image->rootLayer())) { dbgFile << "failed to write layer section. Error:" << layerSection.error << io->pos(); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::ErrorWhileWriting; } } else { // else write a zero length block dbgFile << "No layers, saving empty layers/mask block" << io->pos(); psdwrite(io, (quint32)0); } // IMAGE DATA dbgFile << "Saving composited image" << io->pos(); PSDImageData imagedata(&header); if (!imagedata.write(io, m_image->projection(), haveLayers)) { dbgFile << "Failed to write image data. Error:" << imagedata.error; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::ErrorWhileWriting; } - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } void PSDSaver::cancel() { m_stop = true; } diff --git a/plugins/impex/psd/psd_saver.h b/plugins/impex/psd/psd_saver.h index bf142d5128..f0aaa45c17 100644 --- a/plugins/impex/psd/psd_saver.h +++ b/plugins/impex/psd/psd_saver.h @@ -1,57 +1,62 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _PSD_CONVERTER_H_ #define _PSD_CONVERTER_H_ #include #include #include #include "kis_types.h" -#include +#include + + +// max number of pixels in one dimension of psd file +extern const int MAX_PSD_SIZE; + + class KisDocument; class PSDSaver : public QObject { Q_OBJECT public: PSDSaver(KisDocument *doc); ~PSDSaver() override; public: - KisImageBuilder_Result buildFile(QIODevice *io); + KisImportExportErrorCode buildFile(QIODevice *io); KisImageSP image(); public Q_SLOTS: virtual void cancel(); private: - KisImageSP m_image; KisDocument *m_doc; bool m_stop; }; #endif diff --git a/plugins/impex/psd/tests/data/incorrectFormatFile.txt b/plugins/impex/psd/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/psd/tests/data/readonlyFile.txt b/plugins/impex/psd/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/psd/tests/data/writeonlyFile.txt b/plugins/impex/psd/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/psd/tests/kis_psd_test.cpp b/plugins/impex/psd/tests/kis_psd_test.cpp index f309ef2125..89b1e7100b 100644 --- a/plugins/impex/psd/tests/kis_psd_test.cpp +++ b/plugins/impex/psd/tests/kis_psd_test.cpp @@ -1,351 +1,377 @@ /* * Copyright (C) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_psd_test.h" #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif #include #include "kis_group_layer.h" #include "kis_psd_layer_style.h" #include "kis_paint_device_debug_utils.h" +#include + + + +const QString PSDMimetype = "image/vnd.adobe.photoshop"; void KisPSDTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList()); } void KisPSDTest::testOpening() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "testing_psd_ls.psd"); QScopedPointer doc(qobject_cast(KisPart::instance()->createDocument())); KisImportExportManager manager(doc.data()); doc->setFileBatchMode(true); - KisImportExportFilter::ConversionStatus status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); - QCOMPARE(status, KisImportExportFilter::OK); + KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); + QVERIFY(status.isOk()); Q_ASSERT(doc->image()); } QSharedPointer openPsdDocument(const QFileInfo &fileInfo) { QSharedPointer doc(qobject_cast(KisPart::instance()->createDocument())); KisImportExportManager manager(doc.data()); doc->setFileBatchMode(true); - KisImportExportFilter::ConversionStatus status = manager.importDocument(fileInfo.absoluteFilePath(), QString()); + KisImportExportErrorCode status = manager.importDocument(fileInfo.absoluteFilePath(), QString()); Q_UNUSED(status); return doc; } void KisPSDTest::testTransparencyMask() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/masks.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single", 1, 1)); doc->setFileBatchMode(true); doc->setMimeType("image/vnd.adobe.photoshop"); QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + "test_tmask.psd"); bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), "image/vnd.adobe.photoshop"); QVERIFY(retval); { QSharedPointer doc = openPsdDocument(dstFileInfo); QVERIFY(doc->image()); QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single", 1, 1)); QVERIFY(doc->image()->root()->lastChild()); QVERIFY(doc->image()->root()->lastChild()->firstChild()); QVERIFY(doc->image()->root()->lastChild()->firstChild()->inherits("KisTransparencyMask")); } } void KisPSDTest::testOpenGrayscaleMultilayered() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/gray.psd"); //QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/100x100gray8.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); } void KisPSDTest::testOpenGroupLayers() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "group_layers.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisNodeSP node = TestUtil::findNode(doc->image()->root(), "Group 1 PT"); KisGroupLayer *group = dynamic_cast(node.data()); QVERIFY(group); QVERIFY(group->passThroughMode()); } void KisPSDTest::testOpenLayerStyles() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "testing_psd_ls.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->dropShadow()); QVERIFY(layer->layerStyle()->dropShadow()->effectEnabled()); } void KisPSDTest::testOpenLayerStylesWithPattern() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); } void KisPSDTest::testOpenLayerStylesWithPatternMulti() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern_multi.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); QVERIFY(layer->layerStyle()->stroke()); QVERIFY(layer->layerStyle()->stroke()->effectEnabled()); QVERIFY(layer->layerStyle()->stroke()->pattern()); QVERIFY(layer->layerStyle()->stroke()->pattern()->valid()); } void KisPSDTest::testSaveLayerStylesWithPatternMulti() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern_multi.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); QVERIFY(layer->layerStyle()->stroke()); QVERIFY(layer->layerStyle()->stroke()->effectEnabled()); QVERIFY(layer->layerStyle()->stroke()->pattern()); QVERIFY(layer->layerStyle()->stroke()->pattern()->valid()); doc->setFileBatchMode(true); const QByteArray mimeType("image/vnd.adobe.photoshop"); QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + "test_save_styles.psd"); bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), mimeType); QVERIFY(retval); { QSharedPointer doc = openPsdDocument(dstFileInfo); QVERIFY(doc->image()); QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); //QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single")); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); QVERIFY(layer->layerStyle()->stroke()); QVERIFY(layer->layerStyle()->stroke()->effectEnabled()); QVERIFY(layer->layerStyle()->stroke()->pattern()); QVERIFY(layer->layerStyle()->stroke()->pattern()->valid()); } } void KisPSDTest::testOpeningFromOpenCanvas() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_krita_psd_from_opencanvas.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); QVERIFY(doc->image()->root()->firstChild()); } void KisPSDTest::testOpeningAllFormats() { QString path = TestUtil::fetchExternalDataFileName("psd_format_test_files"); QDir dirSources(path); bool shouldFailTheTest = false; Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { Q_ASSERT(sourceFileInfo.exists()); if (sourceFileInfo.isHidden() || sourceFileInfo.isDir()) { continue; } if (sourceFileInfo.fileName() != "ml_cmyk_16b.psd") { //continue; } //dbgKrita << "Opening" << ppVar(sourceFileInfo.fileName()); QSharedPointer doc = openPsdDocument(sourceFileInfo); if (!doc->image()) { /** * 32bit images are expected to fail atm, their loading is not implemented */ if (!sourceFileInfo.fileName().contains("_32b")) { shouldFailTheTest = true; } errKrita << "FAILED to open" << sourceFileInfo.fileName(); continue; } // just check visually if the file loads fine KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), sourceFileInfo.fileName(), "dd"); } QVERIFY(!shouldFailTheTest); } void KisPSDTest::testSavingAllFormats() { QString path = TestUtil::fetchExternalDataFileName("psd_format_test_files"); QDir dirSources(path); Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { Q_ASSERT(sourceFileInfo.exists()); if (sourceFileInfo.isHidden() || sourceFileInfo.isDir()) { continue; } if (sourceFileInfo.fileName() != "sl_rgb_8b.psd") { //continue; } dbgKrita << "Opening" << ppVar(sourceFileInfo.fileName()); QSharedPointer doc = openPsdDocument(sourceFileInfo); if (!doc->image()) { errKrita << "FAILED to open" << sourceFileInfo.fileName(); continue; } QString baseName = sourceFileInfo.fileName(); //QString originalName = QString("%1_0orig").arg(baseName); //QString resultName = QString("%1_1result").arg(baseName); QString tempPsdName = QString("%1_3interm.psd").arg(baseName); QImage refImage = doc->image()->projection()->convertToQImage(0, QRect(0,0,100,100)); // uncomment to do a visual check // KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), originalName, "dd"); doc->setFileBatchMode(true); doc->setMimeType("image/vnd.adobe.photoshop"); QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + tempPsdName); dbgKrita << "Saving" << ppVar(dstFileInfo.fileName()); bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), "image/vnd.adobe.photoshop"); QVERIFY(retval); { QSharedPointer doc = openPsdDocument(dstFileInfo); QVERIFY(doc->image()); // uncomment to do a visual check //KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), resultName, "dd"); QImage resultImage = doc->image()->projection()->convertToQImage(0, QRect(0,0,100,100)); QCOMPARE(resultImage, refImage); } } } + +void KisPSDTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PSDMimetype); +} + + +void KisPSDTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), PSDMimetype); +} + + +void KisPSDTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), PSDMimetype); +} + + + + KISTEST_MAIN(KisPSDTest) diff --git a/plugins/impex/psd/tests/kis_psd_test.h b/plugins/impex/psd/tests/kis_psd_test.h index 6ed0b7c537..326368d64e 100644 --- a/plugins/impex/psd/tests/kis_psd_test.h +++ b/plugins/impex/psd/tests/kis_psd_test.h @@ -1,45 +1,50 @@ /* * Copyright (C) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_PSD_TEST_H_ #define _KIS_PSD_TEST_H_ #include class KisPSDTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); void testOpening(); void testTransparencyMask(); void testOpenGrayscaleMultilayered(); void testOpenGroupLayers(); void testOpenLayerStyles(); void testOpenLayerStylesWithPattern(); void testOpenLayerStylesWithPatternMulti(); void testSaveLayerStylesWithPatternMulti(); void testOpeningFromOpenCanvas(); void testOpeningAllFormats(); void testSavingAllFormats(); + + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; #endif diff --git a/plugins/impex/qimageio/CMakeLists.txt b/plugins/impex/qimageio/CMakeLists.txt index 1c4c9d8616..07221802c9 100644 --- a/plugins/impex/qimageio/CMakeLists.txt +++ b/plugins/impex/qimageio/CMakeLists.txt @@ -1,25 +1,27 @@ +add_subdirectory(tests) + set(kritaqimageioexport_SOURCES kis_qimageio_export.cpp ) ki18n_wrap_ui(kritaqimageioexport_SOURCES ) add_library(kritaqimageioexport MODULE ${kritaqimageioexport_SOURCES}) target_link_libraries(kritaqimageioexport kritaui kritaimpex) install(TARGETS kritaqimageioexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritaqimageioimport_SOURCES kis_qimageio_import.cpp ) ki18n_wrap_ui(kritaqimageioimport_SOURCES ) add_library(kritaqimageioimport MODULE ${kritaqimageioimport_SOURCES}) target_link_libraries(kritaqimageioimport kritaui) install(TARGETS kritaqimageioimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_qimageio.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/qimageio/kis_qimageio_export.cpp b/plugins/impex/qimageio/kis_qimageio_export.cpp index ee1c2a9aac..55db76bcc3 100644 --- a/plugins/impex/qimageio/kis_qimageio_export.cpp +++ b/plugins/impex/qimageio/kis_qimageio_export.cpp @@ -1,69 +1,69 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_qimageio_export.h" #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisQImageIOExportFactory, "krita_qimageio_export.json", registerPlugin();) KisQImageIOExport::KisQImageIOExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisQImageIOExport::~KisQImageIOExport() { } -KisImportExportFilter::ConversionStatus KisQImageIOExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisQImageIOExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { QRect rc = document->savingImage()->bounds(); QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); - bool r = image.save(io, QFileInfo(filename()).suffix().toLatin1()); - if (r) { - return KisImportExportFilter::OK; + bool result = image.save(io, QFileInfo(filename()).suffix().toLatin1()); + if (result) { + return ImportExportCodes::OK; } else { - return KisImportExportFilter::InvalidFormat; + return ImportExportCodes::FileFormatIncorrect; } } void KisQImageIOExport::initializeCapabilities() { QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, KisMimeDatabase::descriptionForMimeType(mimeType())); addCapability(KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + RGBAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); } #include "kis_qimageio_export.moc" diff --git a/plugins/impex/qimageio/kis_qimageio_export.h b/plugins/impex/qimageio/kis_qimageio_export.h index 6922fa357b..423f9876b5 100644 --- a/plugins/impex/qimageio/kis_qimageio_export.h +++ b/plugins/impex/qimageio/kis_qimageio_export.h @@ -1,38 +1,38 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_BMP_EXPORT_H_ #define _KIS_BMP_EXPORT_H_ #include #include class KisQImageIOExport : public KisImportExportFilter { Q_OBJECT public: KisQImageIOExport(QObject *parent, const QVariantList &); ~KisQImageIOExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/qimageio/kis_qimageio_import.cpp b/plugins/impex/qimageio/kis_qimageio_import.cpp index 73c2b78f0d..411162e8a4 100644 --- a/plugins/impex/qimageio/kis_qimageio_import.cpp +++ b/plugins/impex/qimageio/kis_qimageio_import.cpp @@ -1,73 +1,73 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_qimageio_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisQImageIOImportFactory, "krita_qimageio_import.json", registerPlugin();) KisQImageIOImport::KisQImageIOImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisQImageIOImport::~KisQImageIOImport() { } -KisImportExportFilter::ConversionStatus KisQImageIOImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisQImageIOImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { QFileInfo fi(filename()); QImage img; if (!img.loadFromData(io->readAll(), fi.suffix().toLower().toLatin1())) { - return KisImportExportFilter::InvalidFormat; + return ImportExportCodes::FileFormatIncorrect; } const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(document->createUndoStore(), img.width(), img.height(), colorSpace, i18n("Imported Image")); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); layer->paintDevice()->convertFromQImage(img, 0, 0, 0); image->addNode(layer.data(), image->rootLayer().data()); document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_qimageio_import.moc" diff --git a/plugins/impex/qimageio/kis_qimageio_import.h b/plugins/impex/qimageio/kis_qimageio_import.h index 4e93f74beb..a97eade902 100644 --- a/plugins/impex/qimageio/kis_qimageio_import.h +++ b/plugins/impex/qimageio/kis_qimageio_import.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_BMP_IMPORT_H_ #define _KIS_BMP_IMPORT_H_ #include #include class KisQImageIOImport : public KisImportExportFilter { Q_OBJECT public: KisQImageIOImport(QObject *parent, const QVariantList &); ~KisQImageIOImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/qimageio/tests/CMakeLists.txt b/plugins/impex/qimageio/tests/CMakeLists.txt new file mode 100644 index 0000000000..bed630f553 --- /dev/null +++ b/plugins/impex/qimageio/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisQImageIOTest.cpp + TEST_NAME KisQImageIOTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/qimageio/tests/KisQImageIOTest.cpp similarity index 62% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/qimageio/tests/KisQImageIOTest.cpp index 6ca66688f8..65d4cb9ce2 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/qimageio/tests/KisQImageIOTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "kis_svg_test.h" +#include "KisQImageIOTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString QImageIOMimetype = "image/x-gimp-brush"; + + + +void KisQImageIOTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), QImageIOMimetype); +} + + +void KisQImageIOTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), QImageIOMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisQImageIOTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), QImageIOMimetype); +} + + + +KISTEST_MAIN(KisQImageIOTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/qimageio/tests/KisQImageIOTest.h similarity index 72% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/qimageio/tests/KisQImageIOTest.h index 5c19b0cda6..ea154f3dbc 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/qimageio/tests/KisQImageIOTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _KIS_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_QIMAGEIO_TEST_H_ +#define _KIS_QIMAGEIO_TEST_H_ #include -class KisJpegTest : public QObject +class KisQImageIOTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_QIMAGEIO_TEST_H_ + diff --git a/plugins/impex/qimageio/tests/data/incorrectFormatFile.txt b/plugins/impex/qimageio/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/qimageio/tests/data/readonlyFile.txt b/plugins/impex/qimageio/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/qimageio/tests/data/writeonlyFile.txt b/plugins/impex/qimageio/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/qml/CMakeLists.txt b/plugins/impex/qml/CMakeLists.txt index 2f3826d5d1..e53156905a 100644 --- a/plugins/impex/qml/CMakeLists.txt +++ b/plugins/impex/qml/CMakeLists.txt @@ -1,10 +1,12 @@ +add_subdirectory(tests) + set(kritaqmlexport_SOURCES qml_converter.cc qml_export.cc ) add_library(kritaqmlexport MODULE ${kritaqmlexport_SOURCES}) target_link_libraries(kritaqmlexport kritaui kritaimpex) install(TARGETS kritaqmlexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/impex/qml/qml_converter.cc b/plugins/impex/qml/qml_converter.cc index 8422672254..4d98234f0a 100644 --- a/plugins/impex/qml/qml_converter.cc +++ b/plugins/impex/qml/qml_converter.cc @@ -1,91 +1,96 @@ /* * Copyright (c) 2013 Sven Langkamp * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qml_converter.h" #include #include #include #include #define SPACE " " QMLConverter::QMLConverter() { } QMLConverter::~QMLConverter() { } -KisImageBuilder_Result QMLConverter::buildFile(const QString &filename, const QString &realFilename, QIODevice *io, KisImageSP image) +KisImportExportErrorCode QMLConverter::buildFile(const QString &filename, const QString &realFilename, QIODevice *io, KisImageSP image) { QTextStream out(io); out.setCodec("UTF-8"); out << "import QtQuick 1.1" << "\n\n"; out << "Rectangle {\n"; writeInt(out, 1, "width", image->width()); writeInt(out, 1, "height", image->height()); out << "\n"; QFileInfo info(filename); QFileInfo infoRealFile(realFilename); KisNodeSP node = image->rootLayer()->firstChild(); QString imageDir = infoRealFile.baseName() + "_images"; QString imagePath = infoRealFile.absolutePath() + '/' + imageDir; if (node) { QDir dir; - dir.mkpath(imagePath); + bool success = dir.mkpath(imagePath); + if (!success) + { + return ImportExportCodes::CannotCreateFile; + } } + dbgFile << "Saving images to " << imagePath; while(node) { KisPaintDeviceSP projection = node->projection(); QRect rect = projection->exactBounds(); QImage qmlImage = projection->convertToQImage(0, rect.x(), rect.y(), rect.width(), rect.height()); QString name = node->name().replace(' ', '_').toLower(); QString fileName = name + ".png"; qmlImage.save(imagePath +'/'+ fileName); out << SPACE << "Image {\n"; writeString(out, 2, "id", name); writeInt(out, 2, "x", rect.x()); writeInt(out, 2, "y", rect.y()); writeInt(out, 2, "width", rect.width()); writeInt(out, 2, "height", rect.height()); writeString(out, 2, "source", "\"" + imageDir + '/' + fileName + "\"" ); writeString(out, 2, "opacity", QString().setNum(node->opacity()/255.0)); out << SPACE << "}\n"; node = node->nextSibling(); } out << "}\n"; - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } void QMLConverter::writeString(QTextStream& out, int spacing, const QString& setting, const QString& value) { for (int space = 0; space < spacing; space++) { out << SPACE; } out << setting << ": " << value << "\n"; } void QMLConverter::writeInt(QTextStream& out, int spacing, const QString& setting, int value) { writeString(out, spacing, setting, QString::number(value)); } diff --git a/plugins/impex/qml/qml_converter.h b/plugins/impex/qml/qml_converter.h index e70aa3e176..ab03696947 100644 --- a/plugins/impex/qml/qml_converter.h +++ b/plugins/impex/qml/qml_converter.h @@ -1,44 +1,44 @@ /* * Copyright (c) 2013 Sven Langkamp * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _QML_CONVERTER_H_ #define _QML_CONVERTER_H_ #include #include #include #include "kis_types.h" -#include +#include class QMLConverter : public QObject { Q_OBJECT public: QMLConverter(); ~QMLConverter() override; public: - KisImageBuilder_Result buildFile(const QString &filename, const QString &realFilename, QIODevice *io, KisImageSP image); + KisImportExportErrorCode buildFile(const QString &filename, const QString &realFilename, QIODevice *io, KisImageSP image); private: void writeString(QTextStream& out, int spacing, const QString& setting, const QString& value); void writeInt(QTextStream& out, int spacing, const QString& setting, int value); }; #endif diff --git a/plugins/impex/qml/qml_export.cc b/plugins/impex/qml/qml_export.cc index d70a3278bb..8de5713de9 100644 --- a/plugins/impex/qml/qml_export.cc +++ b/plugins/impex/qml/qml_export.cc @@ -1,73 +1,67 @@ /* * Copyright (c) 2013 Sven Langkamp * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qml_export.h" #include #include #include #include #include #include #include #include #include "qml_converter.h" #include K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_qml_export.json", registerPlugin();) QMLExport::QMLExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } QMLExport::~QMLExport() { } -KisImportExportFilter::ConversionStatus QMLExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode QMLExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisImageSP image = document->savingImage(); Q_CHECK_PTR(image); QMLConverter converter; - KisImageBuilder_Result result = converter.buildFile(filename(), realFilename(), io, image); - if (result == KisImageBuilder_RESULT_OK) { - dbgFile << "success !"; - return KisImportExportFilter::OK; - } - dbgFile << " Result =" << result; - return KisImportExportFilter::InternalError; + return converter.buildFile(filename(), realFilename(), io, image); } void QMLExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "QML"); } #include diff --git a/plugins/impex/qml/qml_export.h b/plugins/impex/qml/qml_export.h index 1cf9b0038d..7ca4de2a36 100644 --- a/plugins/impex/qml/qml_export.h +++ b/plugins/impex/qml/qml_export.h @@ -1,36 +1,36 @@ /* * Copyright (c) 2013 Sven Langkamp * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _QML_EXPORT_H_ #define _QML_EXPORT_H_ #include #include class QMLExport : public KisImportExportFilter { Q_OBJECT public: QMLExport(QObject *parent, const QVariantList &); ~QMLExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/qml/tests/CMakeLists.txt b/plugins/impex/qml/tests/CMakeLists.txt new file mode 100644 index 0000000000..d197d45935 --- /dev/null +++ b/plugins/impex/qml/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisQmlTest.cpp + TEST_NAME KisQmlTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/ppm/tests/kis_ppm_test.cpp b/plugins/impex/qml/tests/KisQmlTest.cpp similarity index 78% copy from plugins/impex/ppm/tests/kis_ppm_test.cpp copy to plugins/impex/qml/tests/KisQmlTest.cpp index 6aa3859d0a..c92533ae76 100644 --- a/plugins/impex/ppm/tests/kis_ppm_test.cpp +++ b/plugins/impex/qml/tests/KisQmlTest.cpp @@ -1,39 +1,43 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "kis_ppm_test.h" +#include "KisQmlTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisPPMTest::testFiles() + +const QString QmlMimetype = "text/x-qml"; + + +void KisQmlTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), QmlMimetype); } -KISTEST_MAIN(KisPPMTest) + +KISTEST_MAIN(KisQmlTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/qml/tests/KisQmlTest.h similarity index 79% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/qml/tests/KisQmlTest.h index 5c19b0cda6..9944e9ca65 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/qml/tests/KisQmlTest.h @@ -1,31 +1,33 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _KIS_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_QML_TEST_H_ +#define _KIS_QML_TEST_H_ #include -class KisJpegTest : public QObject +class KisQmlTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testExportToReadonly(); }; -#endif +#endif // _KIS_QML_TEST_H_ + diff --git a/plugins/impex/qml/tests/data/readonlyFile.txt b/plugins/impex/qml/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/raw/CMakeLists.txt b/plugins/impex/raw/CMakeLists.txt index f03ae6dd5c..f7b63c845c 100644 --- a/plugins/impex/raw/CMakeLists.txt +++ b/plugins/impex/raw/CMakeLists.txt @@ -1,33 +1,35 @@ +add_subdirectory(tests) + if(OPENEXR_FOUND) include_directories(${OPENEXR_INCLUDE_DIR}) endif() include_directories(${LibRaw_INCLUDE_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/libkdcraw/src) set(krita_raw_import_SOURCES kis_raw_import.cpp 3rdparty/libkdcraw/src/dcrawinfocontainer.cpp 3rdparty/libkdcraw/src/dcrawsettingswidget.cpp 3rdparty/libkdcraw/src/kdcraw.cpp 3rdparty/libkdcraw/src/kdcraw_p.cpp 3rdparty/libkdcraw/src/libkdcraw_debug.cpp 3rdparty/libkdcraw/src/ractionjob.cpp 3rdparty/libkdcraw/src/ractionthreadbase.cpp 3rdparty/libkdcraw/src/rawdecodingsettings.cpp 3rdparty/libkdcraw/src/rcombobox.cpp 3rdparty/libkdcraw/src/rexpanderbox.cpp 3rdparty/libkdcraw/src/rnuminput.cpp 3rdparty/libkdcraw/src/rsliderspinbox.cpp 3rdparty/libkdcraw/src/rwidgetutils.cpp 3rdparty/libkdcraw/src/squeezedcombobox.cpp ) ki18n_wrap_ui(krita_raw_import_SOURCES wdgrawimport.ui ) add_library(krita_raw_import MODULE ${krita_raw_import_SOURCES}) target_link_libraries(krita_raw_import kritaui ${LibRaw_LIBRARIES}) install(TARGETS krita_raw_import DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_raw.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/raw/kis_raw_import.cpp b/plugins/impex/raw/kis_raw_import.cpp index 41cdd12c8c..634e4b1e43 100644 --- a/plugins/impex/raw/kis_raw_import.cpp +++ b/plugins/impex/raw/kis_raw_import.cpp @@ -1,192 +1,194 @@ /* * Copyright (c) 2008 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_raw_import.h" #include #include #include #include +#include #include #include "kis_debug.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_transaction.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_iterator_ng.h" #include #include using namespace KDcrawIface; K_PLUGIN_FACTORY_WITH_JSON(KisRawImportFactory, "krita_raw_import.json", registerPlugin();) KisRawImport::KisRawImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { m_dialog = new KoDialog(); m_dialog->enableButtonApply(false); QWidget* widget = new QWidget; m_rawWidget.setupUi(widget); m_dialog->setMainWidget(widget); connect(m_rawWidget.pushButtonUpdate, SIGNAL(clicked()), this, SLOT(slotUpdatePreview())); } KisRawImport::~KisRawImport() { delete m_dialog; } inline quint16 correctIndian(quint16 v) { #if KDCRAW_VERSION < 0x000400 return ((v & 0x00FF) << 8) | ((v & 0xFF00 >> 8)); #else return v; #endif } -KisImportExportFilter::ConversionStatus KisRawImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisRawImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) { // Show dialog m_dialog->setCursor(Qt::ArrowCursor); QApplication::setOverrideCursor(Qt::ArrowCursor); #if KDCRAW_VERSION < 0x010200 m_rawWidget.rawSettings->setDefaultSettings(); #else m_rawWidget.rawSettings->resetToDefault(); #endif slotUpdatePreview(); if (m_dialog->exec() == QDialog::Accepted) { QApplication::setOverrideCursor(Qt::WaitCursor); // Do the decoding // TODO: it would probably be better done in a thread, while an other thread simulate that the application is still living (or even better if libkdcraw was giving us some progress report QByteArray imageData; RawDecodingSettings settings = rawDecodingSettings(); settings.sixteenBitsImage = true; int width, height, rgbmax; KDcraw dcraw; - if (!dcraw.decodeRAWImage(filename(), settings, imageData, width, height, rgbmax)) return KisImportExportFilter::CreationError; + if (!dcraw.decodeRAWImage(filename(), settings, imageData, width, height, rgbmax)) + return ImportExportCodes::FileFormatIncorrect; QApplication::restoreOverrideCursor(); // Init the image const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb16(); KisImageSP image = new KisImage(document->createUndoStore(), width, height, cs, filename()); - if (image.isNull()) return KisImportExportFilter::CreationError; + KIS_ASSERT_RECOVER_RETURN_VALUE(!image.isNull(), ImportExportCodes::InternalError); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), quint8_MAX); image->addNode(layer, image->rootLayer()); - if (layer.isNull()) return KisImportExportFilter::CreationError; + KIS_ASSERT_RECOVER_RETURN_VALUE(!layer.isNull(), ImportExportCodes::InternalError); KisPaintDeviceSP device = layer->paintDevice(); - if (device.isNull()) return KisImportExportFilter::CreationError; + KIS_ASSERT_RECOVER_RETURN_VALUE(!device.isNull(), ImportExportCodes::InternalError); // Copy the data KisHLineIteratorSP it = device->createHLineIteratorNG(0, 0, width); for (int y = 0; y < height; ++y) { do { KoBgrU16Traits::Pixel* pixel = reinterpret_cast(it->rawData()); quint16* ptr = ((quint16*)imageData.data()) + (y * width + it->x()) * 3; #if KDCRAW_VERSION < 0x000400 pixel->red = correctIndian(ptr[2]); pixel->green = correctIndian(ptr[1]); pixel->blue = correctIndian(ptr[0]); #else pixel->red = correctIndian(ptr[0]); pixel->green = correctIndian(ptr[1]); pixel->blue = correctIndian(ptr[2]); #endif pixel->alpha = 0xFFFF; } while (it->nextPixel()); it->nextRow(); } QApplication::restoreOverrideCursor(); document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } QApplication::restoreOverrideCursor(); - return KisImportExportFilter::UserCancelled; + return ImportExportCodes::Cancelled; } void KisRawImport::slotUpdatePreview() { QByteArray imageData; RawDecodingSettings settings = rawDecodingSettings(); settings.sixteenBitsImage = false; int width, height, rgbmax; KDcraw dcraw; if (dcraw.decodeHalfRAWImage(filename(), settings, imageData, width, height, rgbmax)) { QImage image(width, height, QImage::Format_RGB32); for (int y = 0; y < height; ++y) { QRgb *pixel= reinterpret_cast(image.scanLine(y)); for (int x = 0; x < width; ++x) { quint8* ptr = ((quint8*)imageData.data()) + (y * width + x) * 3; pixel[x] = qRgb(ptr[0], ptr[1], ptr[2]); } } m_rawWidget.preview->setPixmap(QPixmap::fromImage(image)); } } RawDecodingSettings KisRawImport::rawDecodingSettings() { #if KDCRAW_VERSION < 0x010200 RawDecodingSettings settings; settings.sixteenBitsImage = true; settings.brightness = m_rawWidget.rawSettings->brightness(); settings.RAWQuality = m_rawWidget.rawSettings->quality(); settings.outputColorSpace = m_rawWidget.rawSettings->outputColorSpace(); settings.RGBInterpolate4Colors = m_rawWidget.rawSettings->useFourColor(); settings.DontStretchPixels = m_rawWidget.rawSettings->useDontStretchPixels(); settings.unclipColors = m_rawWidget.rawSettings->unclipColor(); settings.whiteBalance = m_rawWidget.rawSettings->whiteBalance(); settings.customWhiteBalance = m_rawWidget.rawSettings->customWhiteBalance(); settings.customWhiteBalanceGreen = m_rawWidget.rawSettings->customWhiteBalanceGreen(); settings.enableBlackPoint = m_rawWidget.rawSettings->useBlackPoint(); settings.blackPoint = m_rawWidget.rawSettings->blackPoint(); settings.enableNoiseReduction = m_rawWidget.rawSettings->useNoiseReduction(); settings.NRThreshold = m_rawWidget.rawSettings->NRThreshold(); settings.enableCACorrection = m_rawWidget.rawSettings->useCACorrection(); settings.caMultiplier[0] = m_rawWidget.rawSettings->caRedMultiplier(); settings.caMultiplier[1] = m_rawWidget.rawSettings->caBlueMultiplier(); return settings; #else return m_rawWidget.rawSettings->settings(); #endif } #include "kis_raw_import.moc" diff --git a/plugins/impex/raw/kis_raw_import.h b/plugins/impex/raw/kis_raw_import.h index 0e5843580e..8ae4e42af2 100644 --- a/plugins/impex/raw/kis_raw_import.h +++ b/plugins/impex/raw/kis_raw_import.h @@ -1,58 +1,58 @@ /* * Copyright (c) 2005 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KIS_RAW_IMPORT_H_ #define KIS_RAW_IMPORT_H_ #include #include "ui_wdgrawimport.h" class KoDialog; class WdgRawImport; namespace KDcrawIface { class RawDecodingSettings; } class KisRawImport : public KisImportExportFilter { Q_OBJECT public: KisRawImport(QObject *parent, const QVariantList &); ~KisRawImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; private Q_SLOTS: void slotUpdatePreview(); private: KDcrawIface::RawDecodingSettings rawDecodingSettings(); private: Ui::WdgRawImport m_rawWidget; KoDialog* m_dialog; }; #endif // KIS_RAW_IMPORT_H_ diff --git a/plugins/impex/raw/tests/CMakeLists.txt b/plugins/impex/raw/tests/CMakeLists.txt new file mode 100644 index 0000000000..9057477638 --- /dev/null +++ b/plugins/impex/raw/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisRawTest.cpp + TEST_NAME KisRawTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/raw/tests/KisRawTest.cpp similarity index 70% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/raw/tests/KisRawTest.cpp index 6ca66688f8..ca954e6d6a 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/raw/tests/KisRawTest.cpp @@ -1,40 +1,51 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "kis_svg_test.h" +#include "KisRawTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString RawMimetype = "image/x-krita-raw"; + + + +void KisRawTest::testImportFromWriteonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), RawMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisRawTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), RawMimetype); +} + + + +KISTEST_MAIN(KisRawTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/raw/tests/KisRawTest.h similarity index 76% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/raw/tests/KisRawTest.h index 5c19b0cda6..ec170f963a 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/raw/tests/KisRawTest.h @@ -1,31 +1,34 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _KIS_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_RAW_TEST_H_ +#define _KIS_RAW_TEST_H_ #include -class KisJpegTest : public QObject +class KisRawTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_RAW_TEST_H_ + diff --git a/plugins/impex/raw/tests/data/incorrectFormatFile.txt b/plugins/impex/raw/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/raw/tests/data/readonlyFile.txt b/plugins/impex/raw/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/raw/tests/data/writeonlyFile.txt b/plugins/impex/raw/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/spriter/kis_spriter_export.cpp b/plugins/impex/spriter/kis_spriter_export.cpp index a55c47948a..c5cc2308ad 100644 --- a/plugins/impex/spriter/kis_spriter_export.cpp +++ b/plugins/impex/spriter/kis_spriter_export.cpp @@ -1,617 +1,651 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_spriter_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for KisDegreesToRadians #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisSpriterExportFactory, "krita_spriter_export.json", registerPlugin();) KisSpriterExport::KisSpriterExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisSpriterExport::~KisSpriterExport() { } -bool KisSpriterExport::savePaintDevice(KisPaintDeviceSP dev, const QString &fileName) +KisImportExportErrorCode KisSpriterExport::savePaintDevice(KisPaintDeviceSP dev, const QString &fileName) { QFileInfo fi(fileName); QDir d = fi.absoluteDir(); d.mkpath(d.path()); QRect rc = m_image->bounds().intersected(dev->exactBounds()); if (!KisPNGConverter::isColorSpaceSupported(dev->colorSpace())) { dev = new KisPaintDevice(*dev.data()); dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); } KisPNGOptions options; options.forceSRGB = true; vKisAnnotationSP_it beginIt = m_image->beginAnnotations(); vKisAnnotationSP_it endIt = m_image->endAnnotations(); KisPNGConverter converter(0); - KisImageBuilder_Result res = converter.buildFile(fileName, rc, m_image->xRes(), m_image->yRes(), dev, beginIt, endIt, options, 0); + KisImportExportErrorCode res = converter.buildFile(fileName, rc, m_image->xRes(), m_image->yRes(), dev, beginIt, endIt, options, 0); - return (res == KisImageBuilder_RESULT_OK); + return res; } -void KisSpriterExport::parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId) +KisImportExportErrorCode KisSpriterExport::parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId) { // qDebug() << "parseFolder: parent" << parentGroup->name() // << "folderName" << folderName // << "basepath" << basePath; int currentFolder=0; if(folderId == 0) { folderId = ¤tFolder; } QString pathName; if (!folderName.isEmpty()) { pathName = folderName + "/"; } + KisNodeSP child = parentGroup->lastChild(); while (child) { if (child->visible() && child->inherits("KisGroupLayer")) { - parseFolder(qobject_cast(child.data()), child->name().split(" ").first(), basePath + "/" + pathName, folderId); + KisImportExportErrorCode res = parseFolder(qobject_cast(child.data()), child->name().split(" ").first(), basePath + "/" + pathName, folderId); + if (!res.isOk()) { + return res; + } } child = child->prevSibling(); } Folder folder; folder.id = *folderId; folder.name = folderName; folder.groupName = parentGroup->name(); int fileId = 0; child = parentGroup->lastChild(); + while (child) { if (child->visible() && !child->inherits("KisGroupLayer") && !child->inherits("KisMask")) { QRectF rc = m_image->bounds().intersected(child->exactBounds()); QString layerBaseName = child->name().split(" ").first(); SpriterFile file; file.id = fileId++; file.pathName = pathName; file.baseName = layerBaseName; file.layerName = child->name(); file.name = folderName + "/" + layerBaseName + ".png"; qreal xmin = rc.left(); qreal ymin = rc.top(); qreal xmax = rc.right(); qreal ymax = rc.bottom(); file.width = xmax - xmin; file.height = ymax - ymin; file.x = xmin; file.y = ymin; //qDebug() << "Created file" << file.id << file.name << file.pathName << file.baseName << file.width << file.height << file.layerName; - - savePaintDevice(child->projection(), basePath + file.name); - folder.files.append(file); + KisImportExportErrorCode result = savePaintDevice(child->projection(), basePath + file.name); + if (result.isOk()) { + folder.files.append(file); + } else { + return result; + } } child = child->prevSibling(); } if (folder.files.size() > 0) { //qDebug() << "Adding folder" << folder.id << folder.name << folder.groupName << folder.files.length(); m_folders.append(folder); (*folderId)++; } + + return ImportExportCodes::OK; } Bone *KisSpriterExport::parseBone(const Bone *parent, KisGroupLayerSP groupLayer) { static int boneId = 0; QString groupBaseName = groupLayer->name().split(" ").first(); Bone *bone = new Bone; bone->id = boneId++; bone->parentBone = parent; bone->name = groupBaseName; if (m_boneLayer) { QRectF rc = m_image->bounds().intersected(m_boneLayer->exactBounds()); qreal xmin = rc.left(); qreal ymin = rc.top(); qreal xmax = rc.right(); qreal ymax = rc.bottom(); bone->x = (xmin + xmax) / 2; bone->y = -(ymin + ymax) / 2; bone->width = xmax - xmin; bone->height = ymax - ymin; } else { bone->x = 0.0; bone->y = 0.0; bone->width = 0.0; bone->height = 0.0; } if (parent) { bone->localX = bone->x - parent->x; bone->localY = bone->y - parent->y; } else { bone->localX = bone->x; bone->localY = bone->y; } bone->localAngle = 0.0; bone->localScaleX = 1.0; bone->localScaleY = 1.0; KisNodeSP child = groupLayer->lastChild(); while (child) { if (child->visible() && child->inherits("KisGroupLayer")) { bone->bones.append(parseBone(bone, qobject_cast(child.data()))); } child = child->prevSibling(); } //qDebug() << "Created bone" << bone->id << "with" << bone->bones.size() << "bones"; return bone; } void copyBone(Bone *startBone) { startBone->fixLocalX = startBone->localX; startBone->fixLocalY = startBone->localY; startBone->fixLocalAngle = startBone->localAngle; startBone->fixLocalScaleX= startBone->localScaleX; startBone->fixLocalScaleY= startBone->localScaleY; Q_FOREACH(Bone *child, startBone->bones) { copyBone(child); } } void KisSpriterExport::fixBone(Bone *bone) { qreal boneLocalAngle = 0; qreal boneLocalScaleX = 1; if (bone->bones.length() >= 1) { // if a bone has one or more children, point at first child Bone *childBone = bone->bones[0]; qreal dx = childBone->x - bone->x; qreal dy = childBone->y - bone->y; if (qAbs(dx) > 0 || qAbs(dy) > 0) { boneLocalAngle = KisFastMath::atan2(dy, dx); boneLocalScaleX = sqrt(dx * dx + dy * dy) / 200; } } else if (bone->parentBone) { // else, if bone has parent, point away from parent qreal dx = bone->x - bone->parentBone->x; qreal dy = bone->y - bone->parentBone->y; if (qAbs(dx) > 0 || qAbs(dy) > 0) { boneLocalAngle = KisFastMath::atan2(dy, dx); boneLocalScaleX = sqrt(dx * dx + dy * dy) / 200; } } // adjust bone angle bone->fixLocalAngle += boneLocalAngle; bone->fixLocalScaleX *= boneLocalScaleX; // rotate all the child bones back to world position for (int i = 0; i < bone->bones.length(); ++i) { Bone *childBone = bone->bones[i]; qreal tx = childBone->fixLocalX; qreal ty = childBone->fixLocalY; childBone->fixLocalX = tx * cos(-boneLocalAngle) - ty * sin(-boneLocalAngle); childBone->fixLocalY = tx * sin(-boneLocalAngle) + ty * cos(-boneLocalAngle); childBone->fixLocalX /= boneLocalScaleX; childBone->fixLocalAngle -= boneLocalAngle; childBone->fixLocalScaleX /= boneLocalScaleX; } // rotate all the child objects back to world position for (int i = 0; i < m_objects.length(); ++i) { if (m_objects[i].bone == bone) { m_objects[i].fixLocalAngle -= boneLocalAngle; m_objects[i].fixLocalScaleX /= boneLocalScaleX; } } // process all child bones for (int i = 0; i < bone->bones.length(); ++i) { fixBone(bone->bones[i]); } } void KisSpriterExport::writeBoneRef(const Bone *bone, QDomElement &key, QDomDocument &scml) { if (!bone) return; QDomElement boneRef = scml.createElement("bone_ref"); key.appendChild(boneRef); boneRef.setAttribute("id", bone->id); if (bone->parentBone) { boneRef.setAttribute("parent", bone->parentBone->id); } boneRef.setAttribute("timeline", m_timelineid++); boneRef.setAttribute("key", "0"); Q_FOREACH(const Bone *childBone, bone->bones) { writeBoneRef(childBone, key, scml); } } void KisSpriterExport::writeBone(const Bone *bone, QDomElement &animation, QDomDocument &scml) { if (!bone) return; QDomElement timeline = scml.createElement("timeline"); animation.appendChild(timeline); timeline.setAttribute("id", m_timelineid); timeline.setAttribute("name", bone->name); timeline.setAttribute("object_type", "bone"); QDomElement key = scml.createElement("key"); timeline.appendChild(key); key.setAttribute("id", "0"); key.setAttribute("spin", 0); QDomElement boneEl = scml.createElement("bone"); key.appendChild(boneEl); boneEl.setAttribute("x", QString::number(bone->fixLocalX, 'f', 2)); boneEl.setAttribute("y", QString::number(bone->fixLocalY, 'f', 2)); boneEl.setAttribute("angle", QString::number(bone->fixLocalAngle, 'f', 2)); boneEl.setAttribute("scale_x", QString::number(bone->fixLocalScaleX, 'f', 2)); boneEl.setAttribute("scale_y", QString::number(bone->fixLocalScaleY, 'f', 2)); m_timelineid++; Q_FOREACH(const Bone *childBone, bone->bones) { writeBone(childBone, animation, scml); } } void KisSpriterExport::fillScml(QDomDocument &scml, const QString &entityName) { //qDebug() << "Creating scml" << entityName; QDomElement root = scml.createElement("spriter_data"); scml.appendChild(root); root.setAttribute("scml_version", 1); root.setAttribute("generator", "krita"); root.setAttribute("generator_version", qApp->applicationVersion()); Q_FOREACH(const Folder &folder, m_folders) { QDomElement fe = scml.createElement("folder"); root.appendChild(fe); fe.setAttribute("id", folder.id); fe.setAttribute("name", folder.name); Q_FOREACH(const SpriterFile &file, folder.files) { QDomElement fileElement = scml.createElement("file"); fe.appendChild(fileElement); fileElement.setAttribute("id", file.id); fileElement.setAttribute("name", file.name); fileElement.setAttribute("width", QString::number(file.width, 'f', 2)); fileElement.setAttribute("height", QString::number(file.height, 'f', 2)); // qreal pivotX=0; // qreal pivotY=1; // Q_FOREACH(const SpriterObject &object, m_objects) { // if(file.id == object.fileId) // { // pivotX = (0.0 -(object.fixLocalX / file.width)); // pivotY = (1.0 -(object.fixLocalY / file.height)); // break; // } // } // fileElement.setAttribute("pivot_x", QString::number(pivotX, 'f', 2)); // fileElement.setAttribute("pivot_y", QString::number(pivotY, 'f', 2)); } } // entity QDomElement entity = scml.createElement("entity"); root.appendChild(entity); entity.setAttribute("id", "0"); entity.setAttribute("name", entityName); // entity/animation QDomElement animation = scml.createElement("animation"); entity.appendChild(animation); animation.setAttribute("id", "0"); animation.setAttribute("name", "default"); animation.setAttribute("length", "1000"); animation.setAttribute("looping", "false"); // entity/animation/mainline QDomElement mainline = scml.createElement("mainline"); animation.appendChild(mainline); QDomElement key = scml.createElement("key"); mainline.appendChild(key); key.setAttribute("id", "0"); m_timelineid = 0; writeBoneRef(m_rootBone, key, scml); Q_FOREACH(const SpriterObject &object, m_objects) { QDomElement oe = scml.createElement("object_ref"); key.appendChild(oe); oe.setAttribute("id", object.id); if (object.bone) { oe.setAttribute("parent", object.bone->id); } oe.setAttribute("timeline", m_timelineid++); oe.setAttribute("key", "0"); oe.setAttribute("z_index", object.id); } // entity/animation/timeline m_timelineid = 0; if (m_rootBone) { writeBone(m_rootBone, animation, scml); } Q_FOREACH(const SpriterObject &object, m_objects) { Folder folder; Q_FOREACH(const Folder & f, m_folders) { if (f.id == object.folderId) { folder = f; break; } } SpriterFile file; file.id = -1; Q_FOREACH(const SpriterFile &f, folder.files) { if (f.id == object.fileId) { file = f; break; } } Q_ASSERT(file.id >= 0); QString objectName = "object-" + file.baseName; QDomElement timeline = scml.createElement("timeline"); animation.appendChild(timeline); timeline.setAttribute("id", m_timelineid++); timeline.setAttribute("name", objectName); QDomElement key = scml.createElement("key"); timeline.appendChild(key); key.setAttribute("id", "0"); key.setAttribute("spin", "0"); QDomElement objectEl = scml.createElement("object"); key.appendChild(objectEl); objectEl.setAttribute("folder", object.folderId); objectEl.setAttribute("file", object.fileId); objectEl.setAttribute("x", object.fixLocalX); objectEl.setAttribute("y", object.fixLocalY); objectEl.setAttribute("angle", QString::number(kisRadiansToDegrees(object.fixLocalAngle), 'f', 2)); objectEl.setAttribute("scale_x", QString::number(object.fixLocalScaleX, 'f', 2)); objectEl.setAttribute("scale_y", QString::number(object.fixLocalScaleY, 'f', 2)); } } Bone *findBoneByName(Bone *startBone, const QString &name) { if (!startBone) return 0; //qDebug() << "findBoneByName" << name << "starting with" << startBone->name; if (startBone->name == name) { return startBone; } Q_FOREACH(Bone *child, startBone->bones) { //qDebug() << "looking for" << name << "found" << child->name; if (child->name == name) { return child; } Bone *grandChild = findBoneByName(child, name); if (grandChild){ return grandChild; } } return 0; } -KisImportExportFilter::ConversionStatus KisSpriterExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisSpriterExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { QFileInfo fi(filename()); m_image = document->savingImage(); if (m_image->rootLayer()->childCount() == 0) { - return KisImportExportFilter::UsageError; + return ImportExportCodes::Failure; } KisGroupLayerSP root = m_image->rootLayer(); m_boneLayer = qobject_cast(root->findChildByName("bone").data()); //qDebug() << "Found boneLayer" << m_boneLayer; m_rootLayer= qobject_cast(root->findChildByName("root").data()); //qDebug() << "Fond rootLayer" << m_rootLayer; - parseFolder(m_image->rootLayer(), "", fi.absolutePath()); + KisImportExportErrorCode result = parseFolder(m_image->rootLayer(), "", fi.absolutePath()); + if (!result.isOk()) { + dbgFile << "There were errors encountered while using the spriter exporter."; + return result; + } m_rootBone = 0; if (m_rootLayer) { m_rootBone = parseBone(0, m_rootLayer); } // Generate objects int objectId = 0; for (int folderIndex = 0, folderCount = m_folders.size(); folderIndex < folderCount; ++folderIndex) { Folder folder = m_folders[folderCount - 1 - folderIndex]; for (int fileIndex = 0, fileCount = folder.files.size(); fileIndex < fileCount; ++ fileIndex) { SpriterFile file = folder.files[fileCount - 1 - fileIndex]; SpriterObject spriterObject; spriterObject.id = objectId++; spriterObject.folderId = folder.id; spriterObject.fileId = file.id; spriterObject.x = file.x; spriterObject.y = -file.y; Bone *bone = 0; //qDebug() << "file layername" << file.layerName; // layer.name format: "base_name bone(bone_name) slot(slot_name)" if (file.layerName.contains("bone(")) { int start = file.layerName.indexOf("bone(") + 5; int end = file.layerName.indexOf(')', start); QString boneName = file.layerName.mid(start, end - start); bone = findBoneByName(m_rootBone, boneName); } // layer.name format: "base_name" if (!bone && m_rootBone) { bone = findBoneByName(m_rootBone, file.layerName); } // group.name format: "base_name bone(bone_name)" if (!bone && m_rootBone) { if (folder.groupName.contains("bone(")) { int start = folder.groupName.indexOf("bone(") + 5; int end = folder.groupName.indexOf(')', start); QString boneName = folder.groupName.mid(start, end - start); bone = findBoneByName(m_rootBone, boneName); } // group.name format: "base_name" if (!bone) { bone = findBoneByName(m_rootBone, folder.groupName); } } if (!bone) { bone = m_rootBone; } if (bone) { spriterObject.bone = bone; spriterObject.localX = spriterObject.x - bone->x; spriterObject.localY = spriterObject.y - bone->y; } else { spriterObject.bone = 0; spriterObject.localX = spriterObject.x; spriterObject.localY = spriterObject.y; } spriterObject.localAngle = 0; spriterObject.localScaleX = 1.0; spriterObject.localScaleY = 1.0; SpriterSlot *slot = 0; // layer.name format: "base_name bone(bone_name) slot(slot_name)" if (file.layerName.contains("slot(")) { int start = file.layerName.indexOf("slot(") + 5; int end = file.layerName.indexOf(')', start); slot = new SpriterSlot(); slot->name = file.layerName.mid(start, end - start); slot->defaultAttachmentFlag = file.layerName.contains("*"); } spriterObject.slot = slot; // qDebug() << "Created object" << spriterObject.id << spriterObject.folderId // << spriterObject.fileId << spriterObject.x << spriterObject.y // << spriterObject.localX << spriterObject.localY; m_objects.append(spriterObject); } } // Copy object transforms for (int i = 0; i < m_objects.size(); ++i) { m_objects[i].fixLocalX = m_objects[i].localX; m_objects[i].fixLocalY = m_objects[i].localY; m_objects[i].fixLocalAngle = m_objects[i].localAngle; m_objects[i].fixLocalScaleX = m_objects[i].localScaleX; m_objects[i].fixLocalScaleY = m_objects[i].localScaleY; } // Calculate bone angles if (m_rootBone) { copyBone(m_rootBone); fixBone(m_rootBone); } // Generate scml QDomDocument scml; fillScml(scml, fi.baseName()); - io->write("\n"); - io->write(scml.toString(4).toUtf8()); + bool openedHere = false; + if (!io->isOpen()) { + openedHere = io->open(QIODevice::WriteOnly); + if (!openedHere) { + // unsuccessful open + return ImportExportCodes::NoAccessToWrite; + } + } + + QString towrite = "\n"; + if (io->write(towrite.toUtf8()) != towrite.length()) { + return ImportExportCodes::ErrorWhileWriting; + } + towrite = scml.toString(4).toUtf8(); + if (io->write(towrite.toUtf8()) != towrite.length()) { + return ImportExportCodes::ErrorWhileWriting; + } delete m_rootBone; - return KisImportExportFilter::OK; + if (openedHere) { + // FIXME: casues crash... + //io->close(); + } + + return ImportExportCodes::OK; } void KisSpriterExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "Spriter"); } #include "kis_spriter_export.moc" diff --git a/plugins/impex/spriter/kis_spriter_export.h b/plugins/impex/spriter/kis_spriter_export.h index 5d3c32c3b3..30e123ac76 100644 --- a/plugins/impex/spriter/kis_spriter_export.h +++ b/plugins/impex/spriter/kis_spriter_export.h @@ -1,135 +1,135 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_SPRITER_EXPORT_H_ #define _KIS_SPRITER_EXPORT_H_ #include #include #include #include #include struct SpriterFile { qreal id; QString name; QString pathName; QString baseName; QString layerName; qreal width; qreal height; qreal x; qreal y; }; struct Folder { qreal id; QString name; QString pathName; QString baseName; QString groupName; QList files; }; struct Bone { qreal id; const Bone *parentBone; QString name; qreal x; qreal y; qreal width; qreal height; qreal localX; qreal localY; qreal localAngle; qreal localScaleX; qreal localScaleY; qreal fixLocalX; qreal fixLocalY; qreal fixLocalAngle; qreal fixLocalScaleX; qreal fixLocalScaleY; QList bones; ~Bone() { qDeleteAll(bones); bones.clear(); } }; struct SpriterSlot { QString name; bool defaultAttachmentFlag = false; }; struct SpriterObject { qreal id; qreal folderId; qreal fileId; Bone *bone; SpriterSlot *slot; qreal x; qreal y; qreal localX; qreal localY; qreal localAngle; qreal localScaleX; qreal localScaleY; qreal fixLocalX; qreal fixLocalY; qreal fixLocalAngle; qreal fixLocalScaleX; qreal fixLocalScaleY; ~SpriterObject() { delete slot; } }; class KisSpriterExport : public KisImportExportFilter { Q_OBJECT public: KisSpriterExport(QObject *parent, const QVariantList &); ~KisSpriterExport() override; bool supportsIO() const override { return false; } - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; private: - bool savePaintDevice(KisPaintDeviceSP dev, const QString &fileName); - void parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId = 0); + KisImportExportErrorCode savePaintDevice(KisPaintDeviceSP dev, const QString &fileName); + KisImportExportErrorCode parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId = 0); Bone *parseBone(const Bone *parent, KisGroupLayerSP groupLayer); void fixBone(Bone *bone); void fillScml(QDomDocument &scml, const QString &entityName); void writeBoneRef(const Bone *bone, QDomElement &mainline, QDomDocument &scml); void writeBone(const Bone *bone, QDomElement &timeline, QDomDocument &scml); KisImageSP m_image; qreal m_timelineid; QList m_folders; Bone *m_rootBone; QList m_objects; KisGroupLayerSP m_rootLayer; // Not the image's root later, but the one that is named "root" KisLayerSP m_boneLayer; }; #endif diff --git a/plugins/impex/svg/kis_svg_import.cc b/plugins/impex/svg/kis_svg_import.cc index 86c8ae1cca..bf41af28ca 100644 --- a/plugins/impex/svg/kis_svg_import.cc +++ b/plugins/impex/svg/kis_svg_import.cc @@ -1,103 +1,103 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_svg_import.h" #include #include #include "kis_config.h" #include #include #include #include #include #include "kis_shape_layer.h" #include K_PLUGIN_FACTORY_WITH_JSON(SVGImportFactory, "krita_svg_import.json", registerPlugin();) KisSVGImport::KisSVGImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisSVGImport::~KisSVGImport() { } -KisImportExportFilter::ConversionStatus KisSVGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisSVGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); KisDocument * doc = document; const QString baseXmlDir = QFileInfo(filename()).canonicalPath(); KisConfig cfg(false); qreal resolutionPPI = cfg.preferredVectorImportResolutionPPI(true); if (!batchMode()) { bool okay = false; const QString name = QFileInfo(filename()).fileName(); resolutionPPI = QInputDialog::getInt(0, i18n("Import SVG"), i18n("Enter preferred resolution (PPI) for \"%1\"", name), cfg.preferredVectorImportResolutionPPI(), 0, 100000, 1, &okay); if (!okay) { - return KisImportExportFilter::UserCancelled; + return ImportExportCodes::Cancelled; } cfg.setPreferredVectorImportResolutionPPI(resolutionPPI); } const qreal resolution = resolutionPPI / 72.0; QSizeF fragmentSize; QList shapes = KisShapeLayer::createShapesFromSvg(io, baseXmlDir, QRectF(0,0,1200,800), resolutionPPI, doc->shapeController()->resourceManager(), &fragmentSize); QRectF rawImageRect(QPointF(), fragmentSize); QRect imageRect(rawImageRect.toAlignedRect()); const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(doc->createUndoStore(), imageRect.width(), imageRect.height(), cs, "svg image"); image->setResolution(resolution, resolution); doc->setCurrentImage(image); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), image, i18n("Vector Layer"), OPACITY_OPAQUE_U8); Q_FOREACH (KoShape *shape, shapes) { shapeLayer->addShape(shape); } image->addNode(shapeLayer); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include diff --git a/plugins/impex/svg/kis_svg_import.h b/plugins/impex/svg/kis_svg_import.h index a4d185566e..31e725a151 100644 --- a/plugins/impex/svg/kis_svg_import.h +++ b/plugins/impex/svg/kis_svg_import.h @@ -1,36 +1,36 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_SVG_IMPORT_H_ #define _KIS_SVG_IMPORT_H_ #include #include class KisSVGImport : public KisImportExportFilter { Q_OBJECT public: KisSVGImport(QObject *parent, const QVariantList &); ~KisSVGImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) override; }; #endif diff --git a/plugins/impex/svg/tests/data/incorrectFormatFile.txt b/plugins/impex/svg/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/svg/tests/data/readonlyFile.txt b/plugins/impex/svg/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/svg/tests/data/writeonlyFile.txt b/plugins/impex/svg/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/svg/tests/kis_svg_test.cpp index 6ca66688f8..b407b77a44 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/svg/tests/kis_svg_test.cpp @@ -1,40 +1,57 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_svg_test.h" #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString SvgMimetype = "image/svg+xml"; + + void KisSvgTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); } -KISTEST_MAIN(KisSvgTest) + +void KisSvgTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), SvgMimetype); +} + + + +void KisSvgTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), SvgMimetype); +} + + +KISTEST_MAIN(KisSvgTest) diff --git a/plugins/impex/svg/tests/kis_svg_test.h b/plugins/impex/svg/tests/kis_svg_test.h index 66cf670f4d..e8e481ded8 100644 --- a/plugins/impex/svg/tests/kis_svg_test.h +++ b/plugins/impex/svg/tests/kis_svg_test.h @@ -1,31 +1,34 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_SVG_TEST_H_ #define _KIS_SVG_TEST_H_ #include class KisSvgTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + + void testImportFromWriteonly(); + void testImportIncorrectFormat(); }; #endif diff --git a/plugins/impex/tga/CMakeLists.txt b/plugins/impex/tga/CMakeLists.txt index a9546c3326..b7fe804897 100644 --- a/plugins/impex/tga/CMakeLists.txt +++ b/plugins/impex/tga/CMakeLists.txt @@ -1,24 +1,26 @@ +add_subdirectory(tests) + set(kritatgaexport_SOURCES kis_tga_export.cpp ) ki18n_wrap_ui(kritatgaexport_SOURCES ) add_library(kritatgaexport MODULE ${kritatgaexport_SOURCES}) target_link_libraries(kritatgaexport kritaui kritaimpex) install(TARGETS kritatgaexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritatgaimport_SOURCES kis_tga_import.cpp ) ki18n_wrap_ui(kritatgaimport_SOURCES ) add_library(kritatgaimport MODULE ${kritatgaimport_SOURCES}) target_link_libraries(kritatgaimport kritaui) install(TARGETS kritatgaimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_tga.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/tga/kis_tga_export.cpp b/plugins/impex/tga/kis_tga_export.cpp index ac79779c1f..3d8728e4f3 100644 --- a/plugins/impex/tga/kis_tga_export.cpp +++ b/plugins/impex/tga/kis_tga_export.cpp @@ -1,94 +1,94 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_tga_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "tga.h" K_PLUGIN_FACTORY_WITH_JSON(KisTGAExportFactory, "krita_tga_export.json", registerPlugin();) KisTGAExport::KisTGAExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisTGAExport::~KisTGAExport() { } -KisImportExportFilter::ConversionStatus KisTGAExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisTGAExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); QRect rc = document->savingImage()->bounds(); QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QDataStream s(io); s.setByteOrder(QDataStream::LittleEndian); const QImage& img = image; const bool hasAlpha = (img.format() == QImage::Format_ARGB32); for (int i = 0; i < 12; i++) s << targaMagic[i]; // write header s << quint16(img.width()); // width s << quint16(img.height()); // height s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha) s << quint8(hasAlpha ? 0x24 : 0x20); // top left image (0x20) + 8 bit alpha (0x4) for (int y = 0; y < img.height(); y++) { for (int x = 0; x < img.width(); x++) { const QRgb color = img.pixel(x, y); s << quint8(qBlue(color)); s << quint8(qGreen(color)); s << quint8(qRed(color)); if (hasAlpha) s << quint8(qAlpha(color)); } } - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } void KisTGAExport::initializeCapabilities() { QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "TGA"); } #include "kis_tga_export.moc" diff --git a/plugins/impex/tga/kis_tga_export.h b/plugins/impex/tga/kis_tga_export.h index a10414659e..8faa01d65e 100644 --- a/plugins/impex/tga/kis_tga_export.h +++ b/plugins/impex/tga/kis_tga_export.h @@ -1,38 +1,38 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_TGA_EXPORT_H_ #define _KIS_TGA_EXPORT_H_ #include #include class KisTGAExport : public KisImportExportFilter { Q_OBJECT public: KisTGAExport(QObject *parent, const QVariantList &); ~KisTGAExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/tga/kis_tga_import.cpp b/plugins/impex/tga/kis_tga_import.cpp index e78c4590b4..860f75237d 100644 --- a/plugins/impex/tga/kis_tga_import.cpp +++ b/plugins/impex/tga/kis_tga_import.cpp @@ -1,291 +1,291 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_tga_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisTGAImportFactory, "krita_tga_import.json", registerPlugin();) KisTGAImport::KisTGAImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisTGAImport::~KisTGAImport() { } static QDataStream & operator>> (QDataStream & s, TgaHeader & head) { s >> head.id_length; s >> head.colormap_type; s >> head.image_type; s >> head.colormap_index; s >> head.colormap_length; s >> head.colormap_size; s >> head.x_origin; s >> head.y_origin; s >> head.width; s >> head.height; s >> head.pixel_size; s >> head.flags; /*dbgKrita << "id_length: " << head.id_length << " - colormap_type: " << head.colormap_type << " - image_type: " << head.image_type; dbgKrita << "colormap_index: " << head.colormap_index << " - colormap_length: " << head.colormap_length << " - colormap_size: " << head.colormap_size; dbgKrita << "x_origin: " << head.x_origin << " - y_origin: " << head.y_origin << " - width:" << head.width << " - height:" << head.height << " - pixelsize: " << head.pixel_size << " - flags: " << head.flags;*/ return s; } static bool isSupported(const TgaHeader & head) { if (head.image_type != TGA_TYPE_INDEXED && head.image_type != TGA_TYPE_RGB && head.image_type != TGA_TYPE_GREY && head.image_type != TGA_TYPE_RLE_INDEXED && head.image_type != TGA_TYPE_RLE_RGB && head.image_type != TGA_TYPE_RLE_GREY) { return false; } if (head.image_type == TGA_TYPE_INDEXED || head.image_type == TGA_TYPE_RLE_INDEXED) { if (head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1) { return false; } } if (head.image_type == TGA_TYPE_RGB || head.image_type == TGA_TYPE_GREY || head.image_type == TGA_TYPE_RLE_RGB || head.image_type == TGA_TYPE_RLE_GREY) { if (head.colormap_type != 0) { return false; } } if (head.width == 0 || head.height == 0) { return false; } if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) { return false; } return true; } static bool loadTGA(QDataStream & s, const TgaHeader & tga, QImage &img) { // Create image. img = QImage(tga.width, tga.height, QImage::Format_RGB32); TgaHeaderInfo info(tga); /** * Theoretically, we should check alpha presence via the bits * in flags, but there are a lot of files in the wild that * have this flag unset. It contradicts TGA specification, * but we cannot do anything about it. */ const bool hasAlpha = tga.flags & 0xf; if (tga.pixel_size == 32 && !hasAlpha) { qWarning() << "WARNING: TGA image with 32-bit pixel size reports absence of alpha channel. It is not possible, fixing..."; } if (tga.pixel_size == 32 || tga.pixel_size == 16) { img = QImage(tga.width, tga.height, QImage::Format_ARGB32); } uint pixel_size = (tga.pixel_size / 8); uint size = tga.width * tga.height * pixel_size; if (size < 1) { dbgFile << "This TGA file is broken with size " << size; return false; } // Read palette. char palette[768]; if (info.pal) { // @todo Support palettes in other formats! s.readRawData(palette, 3 * tga.colormap_length); } // Allocate image. uchar * const image = new uchar[size]; if (info.rle) { // Decode image. char * dst = (char *)image; int num = size; while (num > 0) { // Get packet header. uchar c; s >> c; uint count = (c & 0x7f) + 1; num -= count * pixel_size; if (c & 0x80) { // RLE pixels. Q_ASSERT(pixel_size <= 8); char pixel[8]; s.readRawData(pixel, pixel_size); do { memcpy(dst, pixel, pixel_size); dst += pixel_size; } while (--count); } else { // Raw pixels. count *= pixel_size; s.readRawData(dst, count); dst += count; } } } else { // Read raw image. s.readRawData((char *)image, size); } // Convert image to internal format. int y_start, y_step, y_end; if (tga.flags & TGA_ORIGIN_UPPER) { y_start = 0; y_step = 1; y_end = tga.height; } else { y_start = tga.height - 1; y_step = -1; y_end = -1; } uchar* src = image; for (int y = y_start; y != y_end; y += y_step) { QRgb * scanline = (QRgb *) img.scanLine(y); if (info.pal) { // Paletted. for (int x = 0; x < tga.width; x++) { uchar idx = *src++; scanline[x] = qRgb(palette[3 * idx + 2], palette[3 * idx + 1], palette[3 * idx + 0]); } } else if (info.grey) { // Greyscale. for (int x = 0; x < tga.width; x++) { scanline[x] = qRgb(*src, *src, *src); src++; } } else { // True Color. if (tga.pixel_size == 16) { for (int x = 0; x < tga.width; x++) { Color555 c = *reinterpret_cast(src); scanline[x] = qRgb((c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2)); src += 2; } } else if (tga.pixel_size == 24) { for (int x = 0; x < tga.width; x++) { scanline[x] = qRgb(src[2], src[1], src[0]); src += 3; } } else if (tga.pixel_size == 32) { for (int x = 0; x < tga.width; x++) { const uchar alpha = src[3]; scanline[x] = qRgba(src[2], src[1], src[0], alpha); src += 4; } } } } // Free image. delete []image; return true; } -KisImportExportFilter::ConversionStatus KisTGAImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisTGAImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); QDataStream s(io); s.setByteOrder(QDataStream::LittleEndian); TgaHeader tga; s >> tga; s.device()->seek(TgaHeader::SIZE + tga.id_length); // Check image file format. if (s.atEnd()) { - return KisImportExportFilter::InvalidFormat; + return ImportExportCodes::FileFormatIncorrect; } // Check supported file types. if (!isSupported(tga)) { - return KisImportExportFilter::InvalidFormat; + return ImportExportCodes::FileFormatIncorrect; } QImage img; bool result = loadTGA(s, tga, img); if (result == false) { - return KisImportExportFilter::CreationError; + return ImportExportCodes::FileFormatIncorrect; } const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(document->createUndoStore(), img.width(), img.height(), colorSpace, "imported from tga"); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); layer->paintDevice()->convertFromQImage(img, 0, 0, 0); image->addNode(layer.data(), image->rootLayer().data()); document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_tga_import.moc" diff --git a/plugins/impex/tga/kis_tga_import.h b/plugins/impex/tga/kis_tga_import.h index d8ea64383d..a3869bd16d 100644 --- a/plugins/impex/tga/kis_tga_import.h +++ b/plugins/impex/tga/kis_tga_import.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_TGA_IMPORT_H_ #define _KIS_TGA_IMPORT_H_ #include #include class KisTGAImport : public KisImportExportFilter { Q_OBJECT public: KisTGAImport(QObject *parent, const QVariantList &); ~KisTGAImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/tga/tests/CMakeLists.txt b/plugins/impex/tga/tests/CMakeLists.txt new file mode 100644 index 0000000000..7180181703 --- /dev/null +++ b/plugins/impex/tga/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisTgaTest.cpp + TEST_NAME KisTgaTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/tga/tests/KisTgaTest.cpp similarity index 64% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/tga/tests/KisTgaTest.cpp index 6ca66688f8..b3b83d1c05 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/tga/tests/KisTgaTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "kis_svg_test.h" +#include "KisTgaTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString TgaMimetype = "image/x-gimp-brush"; + + + +void KisTgaTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), TgaMimetype); +} + + +void KisTgaTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), TgaMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisTgaTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), TgaMimetype); +} + + + +KISTEST_MAIN(KisTgaTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/tga/tests/KisTgaTest.h similarity index 74% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/tga/tests/KisTgaTest.h index 5c19b0cda6..172bf40374 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/tga/tests/KisTgaTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _KIS_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_TGA_TEST_H_ +#define _KIS_TGA_TEST_H_ #include -class KisJpegTest : public QObject +class KisTgaTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_TGA_TEST_H_ + diff --git a/plugins/impex/tga/tests/data/incorrectFormatFile.txt b/plugins/impex/tga/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/tga/tests/data/readonlyFile.txt b/plugins/impex/tga/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/tga/tests/data/writeonlyFile.txt b/plugins/impex/tga/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/tiff/kis_tiff_converter.cc b/plugins/impex/tiff/kis_tiff_converter.cc index dc873404ab..12806cb7f6 100644 --- a/plugins/impex/tiff/kis_tiff_converter.cc +++ b/plugins/impex/tiff/kis_tiff_converter.cc @@ -1,747 +1,770 @@ /* * Copyright (c) 2005-2006 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_tiff_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tiff_reader.h" #include "kis_tiff_ycbcr_reader.h" #include "kis_buffer_stream.h" #include "kis_tiff_writer_visitor.h" +#include + #if TIFFLIB_VERSION < 20111221 typedef size_t tmsize_t; #endif namespace { QPair getColorSpaceForColorType(uint16 sampletype, uint16 color_type, uint16 color_nb_bits, TIFF *image, uint16 &nbchannels, uint16 &extrasamplescount, uint8 &destDepth) { if (color_type == PHOTOMETRIC_MINISWHITE || color_type == PHOTOMETRIC_MINISBLACK) { if (nbchannels == 0) nbchannels = 1; extrasamplescount = nbchannels - 1; // FIX the extrasamples count in case of if (sampletype == SAMPLEFORMAT_IEEEFP) { if (color_nb_bits == 16) { destDepth = 16; return QPair(GrayAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (color_nb_bits == 32) { destDepth = 32; return QPair(GrayAColorModelID.id(), Float32BitsColorDepthID.id()); } } if (color_nb_bits <= 8) { destDepth = 8; return QPair(GrayAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(GrayAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_RGB /*|| color_type == */) { if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of if (sampletype == SAMPLEFORMAT_IEEEFP) { if (color_nb_bits == 16) { destDepth = 16; return QPair(RGBAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (color_nb_bits == 32) { destDepth = 32; return QPair(RGBAColorModelID.id(), Float32BitsColorDepthID.id()); } return QPair(); } else { if (color_nb_bits <= 8) { destDepth = 8; return QPair(RGBAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(RGBAColorModelID.id(), Integer16BitsColorDepthID.id()); } } } else if (color_type == PHOTOMETRIC_YCBCR) { if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of if (color_nb_bits <= 8) { destDepth = 8; return QPair(YCbCrAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(YCbCrAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_SEPARATED) { if (nbchannels == 0) nbchannels = 4; // SEPARATED is in general CMYK but not always, so we check uint16 inkset; if ((TIFFGetField(image, TIFFTAG_INKSET, &inkset) == 0)) { dbgFile << "Image does not define the inkset."; inkset = 2; } if (inkset != INKSET_CMYK) { dbgFile << "Unsupported inkset (right now, only CMYK is supported)"; char** ink_names; uint16 numberofinks; if (TIFFGetField(image, TIFFTAG_INKNAMES, &ink_names) == 1 && TIFFGetField(image, TIFFTAG_NUMBEROFINKS, &numberofinks) == 1) { dbgFile << "Inks are :"; for (uint i = 0; i < numberofinks; i++) { dbgFile << ink_names[i]; } } else { dbgFile << "inknames are not defined !"; // To be able to read stupid adobe files, if there are no information about inks and four channels, then it's a CMYK file : if (nbchannels - extrasamplescount != 4) { return QPair(); } } } if (color_nb_bits <= 8) { destDepth = 8; return QPair(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_CIELAB || color_type == PHOTOMETRIC_ICCLAB) { destDepth = 16; if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count return QPair(LABAColorModelID.id(), Integer16BitsColorDepthID.id()); } else if (color_type == PHOTOMETRIC_PALETTE) { destDepth = 16; if (nbchannels == 0) nbchannels = 2; extrasamplescount = nbchannels - 2; // FIX the extrasamples count // <-- we will convert the index image to RGBA16 as the palette is always on 16bits colors return QPair(RGBAColorModelID.id(), Integer16BitsColorDepthID.id()); } return QPair(); } } KisPropertiesConfigurationSP KisTIFFOptions::toProperties() const { QHash compToIndex; compToIndex[COMPRESSION_NONE] = 0; compToIndex[COMPRESSION_JPEG] = 1; compToIndex[COMPRESSION_DEFLATE] = 2; compToIndex[COMPRESSION_LZW] = 3; compToIndex[COMPRESSION_PIXARLOG] = 8; KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("compressiontype", compToIndex.value(compressionType, 0)); cfg->setProperty("predictor", predictor - 1); cfg->setProperty("alpha", alpha); cfg->setProperty("flatten", flatten); cfg->setProperty("quality", jpegQuality); cfg->setProperty("deflate", deflateCompress); cfg->setProperty("pixarlog", pixarLogCompress); cfg->setProperty("saveProfile", saveProfile); return cfg; } void KisTIFFOptions::fromProperties(KisPropertiesConfigurationSP cfg) { QHash indexToComp; indexToComp[0] = COMPRESSION_NONE; indexToComp[1] = COMPRESSION_JPEG; indexToComp[2] = COMPRESSION_DEFLATE; indexToComp[3] = COMPRESSION_LZW; indexToComp[4] = COMPRESSION_PIXARLOG; // old value that might be still stored in a config (remove after Krita 5.0 :) ) indexToComp[8] = COMPRESSION_PIXARLOG; compressionType = indexToComp.value( cfg->getInt("compressiontype", 0), COMPRESSION_NONE); predictor = cfg->getInt("predictor", 0) + 1; alpha = cfg->getBool("alpha", true); flatten = cfg->getBool("flatten", true); jpegQuality = cfg->getInt("quality", 80); deflateCompress = cfg->getInt("deflate", 6); pixarLogCompress = cfg->getInt("pixarlog", 6); saveProfile = cfg->getBool("saveProfile", true); } KisTIFFConverter::KisTIFFConverter(KisDocument *doc) { m_doc = doc; m_stop = false; TIFFSetWarningHandler(0); TIFFSetErrorHandler(0); } KisTIFFConverter::~KisTIFFConverter() { } -KisImageBuilder_Result KisTIFFConverter::decode(const QString &filename) +KisImportExportErrorCode KisTIFFConverter::decode(const QString &filename) { dbgFile << "Start decoding TIFF File"; // Opent the TIFF file TIFF *image = 0; + + if (!KisImportExportAdditionalChecks::doesFileExist(filename)) { + return ImportExportCodes::FileNotExist; + } + if (!KisImportExportAdditionalChecks::isFileReadable(filename)) { + return ImportExportCodes::NoAccessToRead; + } + if ((image = TIFFOpen(QFile::encodeName(filename), "r")) == 0) { dbgFile << "Could not open the file, either it does not exist, either it is not a TIFF :" << filename; - return (KisImageBuilder_RESULT_BAD_FETCH); + return (ImportExportCodes::FileFormatIncorrect); } do { dbgFile << "Read new sub-image"; - KisImageBuilder_Result result = readTIFFDirectory(image); - if (result != KisImageBuilder_RESULT_OK) { + KisImportExportErrorCode result = readTIFFDirectory(image); + if (!result.isOk()) { return result; } } while (TIFFReadDirectory(image)); // Freeing memory TIFFClose(image); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -KisImageBuilder_Result KisTIFFConverter::readTIFFDirectory(TIFF* image) +KisImportExportErrorCode KisTIFFConverter::readTIFFDirectory(TIFF* image) { // Read information about the tiff uint32 width, height; if (TIFFGetField(image, TIFFTAG_IMAGEWIDTH, &width) == 0) { dbgFile << "Image does not define its width"; TIFFClose(image); - return KisImageBuilder_RESULT_INVALID_ARG; + return ImportExportCodes::FileFormatIncorrect; } if (TIFFGetField(image, TIFFTAG_IMAGELENGTH, &height) == 0) { dbgFile << "Image does not define its height"; TIFFClose(image); - return KisImageBuilder_RESULT_INVALID_ARG; + return ImportExportCodes::FileFormatIncorrect; } float xres; if (TIFFGetField(image, TIFFTAG_XRESOLUTION, &xres) == 0) { dbgFile << "Image does not define x resolution"; // but we don't stop xres = 100; } float yres; if (TIFFGetField(image, TIFFTAG_YRESOLUTION, &yres) == 0) { dbgFile << "Image does not define y resolution"; // but we don't stop yres = 100; } uint16 depth; if ((TIFFGetField(image, TIFFTAG_BITSPERSAMPLE, &depth) == 0)) { dbgFile << "Image does not define its depth"; depth = 1; } uint16 sampletype; if ((TIFFGetField(image, TIFFTAG_SAMPLEFORMAT, &sampletype) == 0)) { dbgFile << "Image does not define its sample type"; sampletype = SAMPLEFORMAT_UINT; } // Determine the number of channels (useful to know if a file has an alpha or not uint16 nbchannels; if (TIFFGetField(image, TIFFTAG_SAMPLESPERPIXEL, &nbchannels) == 0) { dbgFile << "Image has an undefined number of samples per pixel"; nbchannels = 0; } // Get the number of extrasamples and information about them uint16 *sampleinfo = 0, extrasamplescount; if (TIFFGetField(image, TIFFTAG_EXTRASAMPLES, &extrasamplescount, &sampleinfo) == 0) { extrasamplescount = 0; } // Determine the colorspace uint16 color_type; if (TIFFGetField(image, TIFFTAG_PHOTOMETRIC, &color_type) == 0) { dbgFile << "Image has an undefined photometric interpretation"; color_type = PHOTOMETRIC_MINISWHITE; } uint8 dstDepth = 0; QPair colorSpaceIdTag = getColorSpaceForColorType(sampletype, color_type, depth, image, nbchannels, extrasamplescount, dstDepth); if (colorSpaceIdTag.first.isEmpty()) { dbgFile << "Image has an unsupported colorspace :" << color_type << " for this depth :" << depth; TIFFClose(image); - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } dbgFile << "Colorspace is :" << colorSpaceIdTag.first << colorSpaceIdTag.second << " with a depth of" << depth << " and with a nb of channels of" << nbchannels; // Read image profile dbgFile << "Reading profile"; const KoColorProfile* profile = 0; quint32 EmbedLen; quint8* EmbedBuffer; if (TIFFGetField(image, TIFFTAG_ICCPROFILE, &EmbedLen, &EmbedBuffer) == 1) { dbgFile << "Profile found"; QByteArray rawdata; rawdata.resize(EmbedLen); memcpy(rawdata.data(), EmbedBuffer, EmbedLen); profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceIdTag.first, colorSpaceIdTag.second, rawdata); } const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorSpaceIdTag.first, colorSpaceIdTag.second); // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { dbgFile << "The profile " << profile->name() << " is not compatible with the color space model " << colorSpaceIdTag.first << " " << colorSpaceIdTag.second; profile = 0; } // Do not use the linear gamma profile for 16 bits/channel by default, tiff files are usually created with // gamma correction. XXX: Should we ask the user? if (!profile) { if (colorSpaceIdTag.first == RGBAColorModelID.id()) { profile = KoColorSpaceRegistry::instance()->profileByName("sRGB-elle-V2-srgbtrc.icc"); } else if (colorSpaceIdTag.first == GrayAColorModelID.id()) { profile = KoColorSpaceRegistry::instance()->profileByName("Gray-D50-elle-V2-srgbtrc.icc"); } } // Retrieve a pointer to the colorspace const KoColorSpace* cs = 0; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile:" << profile -> name() << ""; cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, profile); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, 0); } if (cs == 0) { dbgFile << "Colorspace" << colorSpaceIdTag.first << colorSpaceIdTag.second << " is not available, please check your installation."; TIFFClose(image); - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { dbgFile << "The profile can't be used in krita, need conversion"; transform = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Check if there is an alpha channel int8 alphapos = -1; // <- no alpha // Check which extra is alpha if any dbgFile << "There are" << nbchannels << " channels and" << extrasamplescount << " extra channels"; if (sampleinfo) { // index images don't have any sampleinfo, and therefore sampleinfo == 0 for (int i = 0; i < extrasamplescount; i ++) { dbgFile << "sample" << i << "extra sample count" << extrasamplescount << "color channel count" << (cs->colorChannelCount()) << "Number of channels" << nbchannels << "sample info" << sampleinfo[i]; if (sampleinfo[i] == EXTRASAMPLE_UNSPECIFIED) { qWarning() << "Extra sample type not defined for this file, assuming unassociated alpha."; alphapos = i; } if (sampleinfo[i] == EXTRASAMPLE_ASSOCALPHA) { // XXX: dangelo: the color values are already multiplied with // the alpha value. This needs to be reversed later (postprocessor?) qWarning() << "Associated alpha in this file: krita does not handle plremultiplied alpha."; alphapos = i; } if (sampleinfo[i] == EXTRASAMPLE_UNASSALPHA) { // color values are not premultiplied with alpha, and can be used as they are. alphapos = i; } } } dbgFile << "Alpha pos:" << alphapos; // Read META Information KoDocumentInfo * info = m_doc->documentInfo(); char* text; if (TIFFGetField(image, TIFFTAG_ARTIST, &text) == 1) { info->setAuthorInfo("creator", text); } if (TIFFGetField(image, TIFFTAG_DOCUMENTNAME, &text) == 1) { info->setAboutInfo("title", text); } if (TIFFGetField(image, TIFFTAG_IMAGEDESCRIPTION, &text) == 1) { info->setAboutInfo("description", text); } // Get the planar configuration uint16 planarconfig; if (TIFFGetField(image, TIFFTAG_PLANARCONFIG, &planarconfig) == 0) { dbgFile << "Plannar configuration is not define"; TIFFClose(image); - return KisImageBuilder_RESULT_INVALID_ARG; + return ImportExportCodes::FileFormatIncorrect; } // Creating the KisImageSP if (! m_image) { m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, "built image"); m_image->setResolution( POINT_TO_INCH(xres), POINT_TO_INCH(yres )); // It is the "invert" macro because we convert from pointer-per-inchs to points Q_CHECK_PTR(m_image); } else { if (m_image->width() < (qint32)width || m_image->height() < (qint32)height) { quint32 newwidth = (m_image->width() < (qint32)width) ? width : m_image->width(); quint32 newheight = (m_image->height() < (qint32)height) ? height : m_image->height(); m_image->resizeImage(QRect(0,0,newwidth, newheight)); } } KisPaintLayer* layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), quint8_MAX); tdata_t buf = 0; tdata_t* ps_buf = 0; // used only for planar configuration separated KisBufferStreamBase* tiffstream; KisTIFFReaderBase* tiffReader = 0; quint8 poses[5]; KisTIFFPostProcessor* postprocessor = 0; // Configure poses uint8 nbcolorsamples = nbchannels - extrasamplescount; switch (color_type) { case PHOTOMETRIC_MINISWHITE: { poses[0] = 0; poses[1] = 1; postprocessor = new KisTIFFPostProcessorInvert(nbcolorsamples); } break; case PHOTOMETRIC_MINISBLACK: { poses[0] = 0; poses[1] = 1; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_CIELAB: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; postprocessor = new KisTIFFPostProcessorCIELABtoICCLAB(nbcolorsamples); } break; case PHOTOMETRIC_ICCLAB: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_RGB: { if (sampletype == SAMPLEFORMAT_IEEEFP) { poses[2] = 2; poses[1] = 1; poses[0] = 0; poses[3] = 3; } else { poses[0] = 2; poses[1] = 1; poses[2] = 0; poses[3] = 3; } postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_SEPARATED: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; poses[4] = 4; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; default: break; } // Initisalize tiffReader uint16 * lineSizeCoeffs = new uint16[nbchannels]; uint16 vsubsampling = 1; uint16 hsubsampling = 1; for (uint i = 0; i < nbchannels; i++) { lineSizeCoeffs[i] = 1; } if (color_type == PHOTOMETRIC_PALETTE) { uint16 *red; // No need to free them they are free by libtiff uint16 *green; uint16 *blue; if ((TIFFGetField(image, TIFFTAG_COLORMAP, &red, &green, &blue)) == 0) { dbgFile << "Indexed image does not define a palette"; TIFFClose(image); delete [] lineSizeCoeffs; - return KisImageBuilder_RESULT_INVALID_ARG; + return ImportExportCodes::FileFormatIncorrect; } tiffReader = new KisTIFFReaderFromPalette(layer->paintDevice(), red, green, blue, poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor); } else if (color_type == PHOTOMETRIC_YCBCR) { TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRSUBSAMPLING, &hsubsampling, &vsubsampling); lineSizeCoeffs[1] = hsubsampling; lineSizeCoeffs[2] = hsubsampling; uint16 position; TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRPOSITIONING, &position); if (dstDepth == 8) { tiffReader = new KisTIFFYCbCrReaderTarget8Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling); } else if (dstDepth == 16) { tiffReader = new KisTIFFYCbCrReaderTarget16Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling); } } else if (dstDepth == 8) { tiffReader = new KisTIFFReaderTarget8bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor); } else if (dstDepth == 16) { uint16 alphaValue; if (sampletype == SAMPLEFORMAT_IEEEFP) { alphaValue = 15360; // representation of 1.0 in half } else { alphaValue = quint16_MAX; } tiffReader = new KisTIFFReaderTarget16bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue); } else if (dstDepth == 32) { union { float f; uint32 i; } alphaValue; if (sampletype == SAMPLEFORMAT_IEEEFP) { alphaValue.f = 1.0f; } else { alphaValue.i = quint32_MAX; } tiffReader = new KisTIFFReaderTarget32bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue.i); } if (!tiffReader) { delete postprocessor; delete[] lineSizeCoeffs; TIFFClose(image); dbgFile << "Image has an invalid/unsupported color type: " << color_type; - return KisImageBuilder_RESULT_INVALID_ARG; + return ImportExportCodes::FileFormatIncorrect; } if (TIFFIsTiled(image)) { dbgFile << "tiled image"; uint32 tileWidth, tileHeight; uint32 x, y; TIFFGetField(image, TIFFTAG_TILEWIDTH, &tileWidth); TIFFGetField(image, TIFFTAG_TILELENGTH, &tileHeight); uint32 linewidth = (tileWidth * depth * nbchannels) / 8; if (planarconfig == PLANARCONFIG_CONTIG) { buf = _TIFFmalloc(TIFFTileSize(image)); if (depth < 16) { tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, linewidth); } else if (depth < 32) { tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, linewidth); } else { tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, linewidth); } } else { ps_buf = new tdata_t[nbchannels]; uint32 * lineSizes = new uint32[nbchannels]; tmsize_t baseSize = TIFFTileSize(image) / nbchannels; for (uint i = 0; i < nbchannels; i++) { ps_buf[i] = _TIFFmalloc(baseSize); lineSizes[i] = tileWidth; // baseSize / lineSizeCoeffs[i]; } tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes); delete [] lineSizes; } dbgFile << linewidth << "" << nbchannels << "" << layer->paintDevice()->colorSpace()->colorChannelCount(); for (y = 0; y < height; y += tileHeight) { for (x = 0; x < width; x += tileWidth) { dbgFile << "Reading tile x =" << x << " y =" << y; if (planarconfig == PLANARCONFIG_CONTIG) { TIFFReadTile(image, buf, x, y, 0, (tsample_t) - 1); } else { for (uint i = 0; i < nbchannels; i++) { TIFFReadTile(image, ps_buf[i], x, y, 0, i); } } uint32 realTileWidth = (x + tileWidth) < width ? tileWidth : width - x; for (uint yintile = 0; y + yintile < height && yintile < tileHeight / vsubsampling;) { tiffReader->copyDataToChannels(x, y + yintile , realTileWidth, tiffstream); yintile += 1; tiffstream->moveToLine(yintile); } tiffstream->restart(); } } } else { dbgFile << "striped image"; tsize_t stripsize = TIFFStripSize(image); uint32 rowsPerStrip; TIFFGetFieldDefaulted(image, TIFFTAG_ROWSPERSTRIP, &rowsPerStrip); dbgFile << rowsPerStrip << "" << height; rowsPerStrip = qMin(rowsPerStrip, height); // when TIFFNumberOfStrips(image) == 1 it might happen that rowsPerStrip is incorrectly set if (planarconfig == PLANARCONFIG_CONTIG) { buf = _TIFFmalloc(stripsize); if (depth < 16) { tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, stripsize / rowsPerStrip); } else if (depth < 32) { tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, stripsize / rowsPerStrip); } else { tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, stripsize / rowsPerStrip); } } else { ps_buf = new tdata_t[nbchannels]; uint32 scanLineSize = stripsize / rowsPerStrip; dbgFile << " scanLineSize for each plan =" << scanLineSize; uint32 * lineSizes = new uint32[nbchannels]; for (uint i = 0; i < nbchannels; i++) { ps_buf[i] = _TIFFmalloc(stripsize); lineSizes[i] = scanLineSize / lineSizeCoeffs[i]; } tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes); delete [] lineSizes; } dbgFile << "Scanline size =" << TIFFRasterScanlineSize(image) << " / strip size =" << TIFFStripSize(image) << " / rowsPerStrip =" << rowsPerStrip << " stripsize/rowsPerStrip =" << stripsize / rowsPerStrip; uint32 y = 0; dbgFile << " NbOfStrips =" << TIFFNumberOfStrips(image) << " rowsPerStrip =" << rowsPerStrip << " stripsize =" << stripsize; for (uint32 strip = 0; y < height; strip++) { if (planarconfig == PLANARCONFIG_CONTIG) { TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, 0) , buf, (tsize_t) - 1); } else { for (uint i = 0; i < nbchannels; i++) { TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, i), ps_buf[i], (tsize_t) - 1); } } for (uint32 yinstrip = 0 ; yinstrip < rowsPerStrip && y < height ;) { uint linesread = tiffReader->copyDataToChannels(0, y, width, tiffstream); y += linesread; yinstrip += linesread; tiffstream->moveToLine(yinstrip); } tiffstream->restart(); } } tiffReader->finalize(); delete[] lineSizeCoeffs; delete tiffReader; delete tiffstream; if (planarconfig == PLANARCONFIG_CONTIG) { _TIFFfree(buf); } else { for (uint i = 0; i < nbchannels; i++) { _TIFFfree(ps_buf[i]); } delete[] ps_buf; } m_image->addNode(KisNodeSP(layer), m_image->rootLayer().data()); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -KisImageBuilder_Result KisTIFFConverter::buildImage(const QString &filename) +KisImportExportErrorCode KisTIFFConverter::buildImage(const QString &filename) { return decode(filename); } KisImageSP KisTIFFConverter::image() { return m_image; } -KisImageBuilder_Result KisTIFFConverter::buildFile(const QString &filename, KisImageSP kisimage, KisTIFFOptions options) +KisImportExportErrorCode KisTIFFConverter::buildFile(const QString &filename, KisImageSP kisimage, KisTIFFOptions options) { dbgFile << "Start writing TIFF File"; - if (!kisimage) - return KisImageBuilder_RESULT_EMPTY; + KIS_ASSERT_RECOVER_RETURN_VALUE(kisimage, ImportExportCodes::InternalError); // Open file for writing TIFF *image; if ((image = TIFFOpen(QFile::encodeName(filename), "w")) == 0) { dbgFile << "Could not open the file for writing" << filename; - TIFFClose(image); - return (KisImageBuilder_RESULT_FAILURE); + return ImportExportCodes::NoAccessToWrite; } // Set the document information KoDocumentInfo * info = m_doc->documentInfo(); QString title = info->aboutInfo("title"); if (!title.isEmpty()) { - TIFFSetField(image, TIFFTAG_DOCUMENTNAME, title.toLatin1().constData()); + if (!TIFFSetField(image, TIFFTAG_DOCUMENTNAME, title.toLatin1().constData())) { + TIFFClose(image); + return ImportExportCodes::ErrorWhileWriting; + } } QString abstract = info->aboutInfo("description"); if (!abstract.isEmpty()) { - TIFFSetField(image, TIFFTAG_IMAGEDESCRIPTION, abstract.toLatin1().constData()); + if (!TIFFSetField(image, TIFFTAG_IMAGEDESCRIPTION, abstract.toLatin1().constData())) { + TIFFClose(image); + return ImportExportCodes::ErrorWhileWriting; + } } QString author = info->authorInfo("creator"); if (!author.isEmpty()) { - TIFFSetField(image, TIFFTAG_ARTIST, author.toLatin1().constData()); + if(!TIFFSetField(image, TIFFTAG_ARTIST, author.toLatin1().constData())) { + TIFFClose(image); + return ImportExportCodes::ErrorWhileWriting; + } } dbgFile << "xres: " << INCH_TO_POINT(kisimage->xRes()) << " yres: " << INCH_TO_POINT(kisimage->yRes()); - TIFFSetField(image, TIFFTAG_XRESOLUTION, INCH_TO_POINT(kisimage->xRes())); // It is the "invert" macro because we convert from pointer-per-inchs to points - TIFFSetField(image, TIFFTAG_YRESOLUTION, INCH_TO_POINT(kisimage->yRes())); + if (!TIFFSetField(image, TIFFTAG_XRESOLUTION, INCH_TO_POINT(kisimage->xRes()))) { // It is the "invert" macro because we convert from pointer-per-inchs to points + TIFFClose(image); + return ImportExportCodes::ErrorWhileWriting; + } + if (!TIFFSetField(image, TIFFTAG_YRESOLUTION, INCH_TO_POINT(kisimage->yRes()))) { + TIFFClose(image); + return ImportExportCodes::ErrorWhileWriting; + } KisGroupLayer* root = dynamic_cast(kisimage->rootLayer().data()); - if (root == 0) { + KIS_ASSERT_RECOVER(root) { TIFFClose(image); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::InternalError; } KisTIFFWriterVisitor* visitor = new KisTIFFWriterVisitor(image, &options); - if (!visitor->visit(root)) { + KIS_ASSERT_RECOVER(visitor->visit(root)) { TIFFClose(image); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::InternalError; } TIFFClose(image); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } void KisTIFFConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/tiff/kis_tiff_converter.h b/plugins/impex/tiff/kis_tiff_converter.h index 47298c85c3..af064a56bd 100644 --- a/plugins/impex/tiff/kis_tiff_converter.h +++ b/plugins/impex/tiff/kis_tiff_converter.h @@ -1,71 +1,71 @@ /* * Copyright (c) 2005-2006 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_TIFF_CONVERTER_H_ #define _KIS_TIFF_CONVERTER_H_ #include #include #include #include "kis_types.h" #include "kis_global.h" #include "kis_annotation.h" -#include +#include class KisDocument; struct KisTIFFOptions { quint16 compressionType = 0; quint16 predictor = 1; bool alpha = true; bool flatten = true; quint16 jpegQuality = 80; quint16 deflateCompress = 6; quint16 pixarLogCompress = 6; bool saveProfile = true; KisPropertiesConfigurationSP toProperties() const; void fromProperties(KisPropertiesConfigurationSP cfg); }; class KisTIFFConverter : public QObject { Q_OBJECT public: KisTIFFConverter(KisDocument *doc); ~KisTIFFConverter() override; public: - KisImageBuilder_Result buildImage(const QString &filename); - KisImageBuilder_Result buildFile(const QString &filename, KisImageSP layer, KisTIFFOptions); + KisImportExportErrorCode buildImage(const QString &filename); + KisImportExportErrorCode buildFile(const QString &filename, KisImageSP layer, KisTIFFOptions); /** Retrieve the constructed image */ KisImageSP image(); public Q_SLOTS: virtual void cancel(); private: - KisImageBuilder_Result decode(const QString &filename); - KisImageBuilder_Result readTIFFDirectory(TIFF* image); + KisImportExportErrorCode decode(const QString &filename); + KisImportExportErrorCode readTIFFDirectory(TIFF* image); private: KisImageSP m_image; KisDocument *m_doc; bool m_stop; }; #endif diff --git a/plugins/impex/tiff/kis_tiff_export.cc b/plugins/impex/tiff/kis_tiff_export.cc index 1bf81d4965..f045bb455f 100644 --- a/plugins/impex/tiff/kis_tiff_export.cc +++ b/plugins/impex/tiff/kis_tiff_export.cc @@ -1,144 +1,138 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_tiff_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" #include "kis_tiff_converter.h" #include "kis_dlg_options_tiff.h" #include "ui_kis_wdg_options_tiff.h" K_PLUGIN_FACTORY_WITH_JSON(KisTIFFExportFactory, "krita_tiff_export.json", registerPlugin();) KisTIFFExport::KisTIFFExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisTIFFExport::~KisTIFFExport() { } -KisImportExportFilter::ConversionStatus KisTIFFExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisTIFFExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration) { // If a configuration object was passed to the convert method, we use that, otherwise we load from the settings KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); if (configuration) { cfg->fromXML(configuration->toXML()); } else { cfg = lastSavedConfiguration(KisDocument::nativeFormatMimeType(), "image/tiff"); } const KoColorSpace* cs = document->savingImage()->colorSpace(); cfg->setProperty("type", (int)cs->channels()[0]->channelValueType()); cfg->setProperty("isCMYK", (cs->colorModelId() == CMYKAColorModelID)); KisTIFFOptions options; options.fromProperties(configuration); if (!options.flatten) { const bool hasGroupLayers = KisLayerUtils::recursiveFindNode(document->savingImage()->root(), [] (KisNodeSP node) { return node->parent() && node->inherits("KisGroupLayer"); }); options.flatten = hasGroupLayers; } if ((cs->channels()[0]->channelValueType() == KoChannelInfo::FLOAT16 || cs->channels()[0]->channelValueType() == KoChannelInfo::FLOAT32) && options.predictor == 2) { // FIXME THIS IS AN HACK FIX THAT IN 2.0 !! (62456a7b47636548c6507593df3e2bdf440f7544, BUG:135649) options.predictor = 3; } KisImageSP image; if (options.flatten) { image = new KisImage(0, document->savingImage()->width(), document->savingImage()->height(), document->savingImage()->colorSpace(), ""); image->setResolution(document->savingImage()->xRes(), document->savingImage()->yRes()); KisPaintDeviceSP pd = KisPaintDeviceSP(new KisPaintDevice(*document->savingImage()->projection())); KisPaintLayerSP l = KisPaintLayerSP(new KisPaintLayer(image.data(), "projection", OPACITY_OPAQUE_U8, pd)); image->addNode(KisNodeSP(l.data()), image->rootLayer().data()); } else { image = document->savingImage(); } KisTIFFConverter tiffConverter(document); - KisImageBuilder_Result res; - if ((res = tiffConverter.buildFile(filename(), image, options)) == KisImageBuilder_RESULT_OK) { - dbgFile << "success !"; - return KisImportExportFilter::OK; - } - - dbgFile << " Result =" << res; - return KisImportExportFilter::InternalError; + KisImportExportErrorCode res = tiffConverter.buildFile(filename(), image, options); + return res; } KisPropertiesConfigurationSP KisTIFFExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisTIFFOptions options; return options.toProperties(); } KisConfigWidget *KisTIFFExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisTIFFOptionsWidget(parent); } void KisTIFFExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(RGBAColorModelID, Float16BitsColorDepthID) << QPair(RGBAColorModelID, Float32BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID) << QPair(CMYKAColorModelID, Integer8BitsColorDepthID) << QPair(CMYKAColorModelID, Integer16BitsColorDepthID) << QPair(LABAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "TIFF"); } #include diff --git a/plugins/impex/tiff/kis_tiff_export.h b/plugins/impex/tiff/kis_tiff_export.h index 7a3d024719..76afc237e2 100644 --- a/plugins/impex/tiff/kis_tiff_export.h +++ b/plugins/impex/tiff/kis_tiff_export.h @@ -1,41 +1,41 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_TIFF_EXPORT_H_ #define _KIS_TIFF_EXPORT_H_ #include #include #include class KisTIFFExport : public KisImportExportFilter { Q_OBJECT public: KisTIFFExport(QObject *parent, const QVariantList &); ~KisTIFFExport() override; bool supportsIO() const override { return false; } - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/tiff/kis_tiff_import.cc b/plugins/impex/tiff/kis_tiff_import.cc index c3d795dc07..eb947dc135 100644 --- a/plugins/impex/tiff/kis_tiff_import.cc +++ b/plugins/impex/tiff/kis_tiff_import.cc @@ -1,72 +1,54 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_tiff_import.h" #include #include #include #include #include #include "kis_tiff_converter.h" K_PLUGIN_FACTORY_WITH_JSON(TIFFImportFactory, "krita_tiff_import.json", registerPlugin();) KisTIFFImport::KisTIFFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisTIFFImport::~KisTIFFImport() { } -KisImportExportFilter::ConversionStatus KisTIFFImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisTIFFImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) { KisTIFFConverter tiffConverter(document); - - switch (tiffConverter.buildImage(filename())) { - case KisImageBuilder_RESULT_UNSUPPORTED: - return KisImportExportFilter::NotImplemented; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_LOCAL: - return KisImportExportFilter::FileNotFound; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InternalError; - case KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE: - return KisImportExportFilter::WrongFormat; - case KisImageBuilder_RESULT_OK: - document -> setCurrentImage(tiffConverter.image()); - return KisImportExportFilter::OK; - default: - break; + KisImportExportErrorCode result = tiffConverter.buildImage(filename()); + if (result.isOk()) { + document->setCurrentImage(tiffConverter.image()); } - return KisImportExportFilter::InternalError; + return result; } #include diff --git a/plugins/impex/tiff/kis_tiff_import.h b/plugins/impex/tiff/kis_tiff_import.h index 03d54f6be8..ddfaf0bfd7 100644 --- a/plugins/impex/tiff/kis_tiff_import.h +++ b/plugins/impex/tiff/kis_tiff_import.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2005 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_TIFF_IMPORT_H_ #define _KIS_TIFF_IMPORT_H_ #include #include class KisTIFFImport : public KisImportExportFilter { Q_OBJECT public: KisTIFFImport(QObject *parent, const QVariantList &); ~KisTIFFImport() override; bool supportsIO() const override { return false; } public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/tiff/tests/data/incorrectFormatFile.txt b/plugins/impex/tiff/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/tiff/tests/data/readonlyFile.txt b/plugins/impex/tiff/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/tiff/tests/data/writeonlyFile.txt b/plugins/impex/tiff/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/tiff/tests/kis_tiff_test.cpp b/plugins/impex/tiff/tests/kis_tiff_test.cpp index 0ce5fcb26c..111eafcfd2 100644 --- a/plugins/impex/tiff/tests/kis_tiff_test.cpp +++ b/plugins/impex/tiff/tests/kis_tiff_test.cpp @@ -1,119 +1,141 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tiff_test.h" #include #include #include "filestest.h" #include #include #include "kisexiv2/kis_exiv2.h" #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString TiffMimetype = "image/tiff"; + void KisTiffTest::testFiles() { // XXX: make the exiv io backends real plugins KisExiv2::initialize(); QStringList excludes; #ifndef CPU_32_BITS excludes << "flower-minisblack-06.tif"; #endif #ifdef HAVE_LCMS2 excludes << "flower-separated-contig-08.tif" << "flower-separated-contig-16.tif" << "flower-separated-planar-08.tif" << "flower-separated-planar-16.tif" << "flower-minisblack-02.tif" << "flower-minisblack-04.tif" << "flower-minisblack-08.tif" << "flower-minisblack-10.tif" << "flower-minisblack-12.tif" << "flower-minisblack-14.tif" << "flower-minisblack-16.tif" << "flower-minisblack-24.tif" << "flower-minisblack-32.tif" << "jim___dg.tif" << "jim___gg.tif" << "strike.tif"; #endif excludes << "text.tif" << "ycbcr-cat.tif"; TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", excludes, QString(), 1); } void KisTiffTest::testRoundTripRGBF16() { // Disabled for now, it's broken because we assumed integers. #if 0 QRect testRect(0,0,1000,1000); QRect fillRect(100,100,100,100); const KoColorSpace *csf16 = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float16BitsColorDepthID.id(), 0); KisDocument *doc0 = qobject_cast(KisPart::instance()->createDocument()); doc0->newImage("test", testRect.width(), testRect.height(), csf16, KoColor(Qt::blue, csf16), QString(), 1.0); QTemporaryFile tmpFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".tiff")); tmpFile.open(); doc0->setBackupFile(false); doc0->setOutputMimeType("image/tiff"); doc0->setFileBatchMode(true); doc0->saveAs(QUrl::fromLocalFile(tmpFile.fileName())); KisNodeSP layer0 = doc0->image()->root()->firstChild(); Q_ASSERT(layer0); layer0->paintDevice()->fill(fillRect, KoColor(Qt::red, csf16)); KisDocument *doc1 = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc1); doc1->setFileBatchMode(false); - KisImportExportFilter::ConversionStatus status; + KisImportExportErrorCode status; QString s = manager.importDocument(tmpFile.fileName(), QString(), status); dbgKrita << s; Q_ASSERT(doc1->image()); QImage ref0 = doc0->image()->projection()->convertToQImage(0, testRect); QImage ref1 = doc1->image()->projection()->convertToQImage(0, testRect); QCOMPARE(ref0, ref1); #endif } + +void KisTiffTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), TiffMimetype); +} + + +void KisTiffTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), TiffMimetype, true); +} + + +void KisTiffTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), TiffMimetype); +} + + + KISTEST_MAIN(KisTiffTest) diff --git a/plugins/impex/tiff/tests/kis_tiff_test.h b/plugins/impex/tiff/tests/kis_tiff_test.h index c86d906d4b..11505376f7 100644 --- a/plugins/impex/tiff/tests/kis_tiff_test.h +++ b/plugins/impex/tiff/tests/kis_tiff_test.h @@ -1,32 +1,36 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_TIFF_TEST_H_ #define _KIS_TIFF_TEST_H_ #include class KisTiffTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); void testRoundTripRGBF16(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; #endif diff --git a/plugins/impex/xcf/3rdparty/xcftools/flatspec.c b/plugins/impex/xcf/3rdparty/xcftools/flatspec.c index ec12a67faf..54c74d90aa 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/flatspec.c +++ b/plugins/impex/xcf/3rdparty/xcftools/flatspec.c @@ -1,380 +1,397 @@ /* Flattening selections function for xcftools * * This file was written by Henning Makholm * It is hereby in the public domain. * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #include "xcftools.h" #include "flatten.h" #include #include void init_flatspec(struct FlattenSpec *spec) { spec->window_mode = USE_CANVAS ; spec->default_pixel = PERHAPS_ALPHA_CHANNEL ; spec->numLayers = 0 ; spec->layers = NULL ; spec->transmap_filename = NULL ; spec->output_filename = "-" ; spec->out_color_mode = COLOR_BY_CONTENTS ; spec->partial_transparency_mode = ALLOW_PARTIAL_TRANSPARENCY ; spec->process_in_memory = 0 ; spec->gimpish_indexed = 1 ; } -void +int add_layer_request(struct FlattenSpec *spec, const char *layer) { spec->layers = realloc(spec->layers, sizeof(struct xcfLayer) * (1+spec->numLayers)); - if( spec->layers == NULL ) + if( spec->layers == NULL ) { FatalUnexpected(_("Out of memory")); + return XCF_ERROR; + } spec->layers[spec->numLayers].name = layer ; spec->layers[spec->numLayers].mode = (GimpLayerModeEffects)-1 ; spec->layers[spec->numLayers].opacity = 9999 ; spec->layers[spec->numLayers].hasMask = -1 ; spec->numLayers++ ; + return XCF_OK; } struct xcfLayer * lastlayerspec(struct FlattenSpec *spec,const char *option) { - if( spec->numLayers == 0 ) + if( spec->numLayers == 0 ) { FatalGeneric(20,_("The %s option must follow a layer name on the " "command line"),option); + return XCF_PTR_EMPTY; + } return spec->layers + (spec->numLayers-1) ; } static int typeHasTransparency(GimpImageType type) { switch( type ) { case GIMP_RGB_IMAGE: case GIMP_GRAY_IMAGE: case GIMP_INDEXED_IMAGE: return 0 ; case GIMP_RGBA_IMAGE: case GIMP_GRAYA_IMAGE: case GIMP_INDEXEDA_IMAGE: return 1 ; } return 1 ; } static enum out_color_mode color_by_layers(struct FlattenSpec *spec) { int colormap_is_colored = 0 ; enum out_color_mode grayish ; int i ; if( spec->default_pixel == CHECKERED_BACKGROUND ) grayish = COLOR_GRAY ; else { int degrayed = degrayPixel(spec->default_pixel); if( degrayed < 0 ) { return COLOR_RGB ; } else if( spec->gimpish_indexed && (degrayed == 0 || degrayed == 255) ) { grayish = COLOR_MONO ; } else { grayish = COLOR_GRAY ; } } for( i=0; inumLayers; i++ ) switch( spec->layers[i].type ) { case GIMP_RGB_IMAGE: case GIMP_RGBA_IMAGE: return COLOR_RGB ; case GIMP_GRAY_IMAGE: case GIMP_GRAYA_IMAGE: grayish = COLOR_GRAY ; break ; case GIMP_INDEXED_IMAGE: case GIMP_INDEXEDA_IMAGE: if( colormap_is_colored ) return COLOR_RGB ; break ; } return grayish ; } -void +int complete_flatspec(struct FlattenSpec *spec, guesser guess_callback) { unsigned i ; int anyPartial ; /* Find the layers to convert. */ if( spec->numLayers == 0 ) { spec->layers = XCF.layers ; spec->numLayers = XCF.numLayers ; } else { for( i=0; inumLayers; i++ ) { GimpLayerModeEffects mode ; int opacity, hasMask ; unsigned j ; for( j=0; ; j++ ) { - if( j == XCF.numLayers ) + if( j == XCF.numLayers ) { FatalGeneric(22,_("The image has no layer called '%s'"), spec->layers[i].name); + return XCF_ERROR; + } if( strcmp(spec->layers[i].name,XCF.layers[j].name) == 0 ) break ; } mode = spec->layers[i].mode == (GimpLayerModeEffects)-1 ? XCF.layers[j].mode : spec->layers[i].mode ; opacity = spec->layers[i].opacity == 9999 ? XCF.layers[j].opacity : spec->layers[i].opacity ; hasMask = spec->layers[i].hasMask == -1 ? XCF.layers[j].hasMask : spec->layers[i].hasMask ; if( hasMask && !XCF.layers[j].hasMask && - XCF.layers[j].mask.hierarchy == 0 ) + XCF.layers[j].mask.hierarchy == 0 ) { FatalGeneric(22,_("Layer '%s' has no layer mask to enable"), spec->layers[i].name); + return XCF_ERROR; + } spec->layers[i] = XCF.layers[j] ; spec->layers[i].mode = mode ; spec->layers[i].opacity = opacity ; spec->layers[i].hasMask = hasMask ; spec->layers[i].isVisible = 1 ; } } /* Force the mode of the lowest visible layer to be Normal or Dissolve. * That may not be logical, but the Gimp does it */ for( i=0; i < spec->numLayers; i++ ) { if( spec->layers[i].isVisible ) { if( spec->layers[i].mode != GIMP_DISSOLVE_MODE ) spec->layers[i].mode = GIMP_NORMAL_MODE ; break ; } } /* Mimic the Gimp's behavior on indexed layers */ if( XCF.type == GIMP_INDEXED && spec->gimpish_indexed ) { for( i=0; inumLayers; i++ ) if( spec->layers[i].mode != GIMP_DISSOLVE_MODE ) spec->layers[i].mode = GIMP_NORMAL_NOPARTIAL_MODE ; } else spec->gimpish_indexed = 0 ; /* compute dimensions of the window */ if( spec->window_mode == AUTOCROP ) { int first = 1 ; for( i=0; inumLayers; i++ ) if( spec->layers[i].isVisible ) { computeDimensions(&spec->layers[i].dim) ; if( first ) { spec->dim = spec->layers[i].dim ; first = 0 ; } else { if( spec->dim.c.l > spec->layers[i].dim.c.l ) spec->dim.c.l = spec->layers[i].dim.c.l ; if( spec->dim.c.r < spec->layers[i].dim.c.r ) spec->dim.c.r = spec->layers[i].dim.c.r ; if( spec->dim.c.t > spec->layers[i].dim.c.t ) spec->dim.c.t = spec->layers[i].dim.c.t ; if( spec->dim.c.b < spec->layers[i].dim.c.b ) spec->dim.c.b = spec->layers[i].dim.c.b ; } } if( first ) { spec->window_mode = USE_CANVAS ; } else { spec->dim.width = spec->dim.c.r - spec->dim.c.l ; spec->dim.height = spec->dim.c.b - spec->dim.c.t ; } } if( spec->window_mode != AUTOCROP ) { if( (spec->window_mode & MANUAL_OFFSET) == 0 ) spec->dim.c.t = spec->dim.c.l = 0 ; if( (spec->window_mode & MANUAL_CROP) == 0 ) { spec->dim.height = XCF.height ; spec->dim.width = XCF.width ; } } computeDimensions(&spec->dim); /* Turn off layers that we don't hit at all */ for( i=0; inumLayers; i++ ) if( spec->layers[i].isVisible && disjointRects(spec->dim.c,spec->layers[i].dim.c) ) spec->layers[i].isVisible = 0 ; /* See if there is a completely covering layer somewhere in the stack */ /* Also check if partial transparency is possible */ anyPartial = 0 ; for( i=spec->numLayers; i-- ; ) { if( !spec->layers[i].isVisible ) continue ; if( typeHasTransparency(spec->layers[i].type) ) { if( spec->layers[i].mode == GIMP_NORMAL_MODE ) anyPartial = 1; } else if( isSubrect(spec->dim.c,spec->layers[i].dim.c) && !spec->layers[i].hasMask && (spec->layers[i].mode == GIMP_NORMAL_MODE || spec->layers[i].mode == GIMP_NORMAL_NOPARTIAL_MODE || spec->layers[i].mode == GIMP_DISSOLVE_MODE) ) { /* This layer fills out the entire image. * Turn off only lower layers, and note that we cannot have * transparency at all. */ while(i) spec->layers[--i].isVisible = 0 ; if( spec->default_pixel != FORCE_ALPHA_CHANNEL ) spec->default_pixel = NEWALPHA(colormap[0],255); anyPartial = 0 ; break ; } } if( spec->partial_transparency_mode == ALLOW_PARTIAL_TRANSPARENCY && (!anyPartial || ALPHA(spec->default_pixel) >= 128) ) spec->partial_transparency_mode = PARTIAL_TRANSPARENCY_IMPOSSIBLE ; /* Initialize layers and print overview if we're verbose */ for( i=spec->numLayers; i--; ) if( spec->layers[i].isVisible ) { - initLayer(&spec->layers[i]) ; + if (initLayer(&spec->layers[i]) != XCF_OK) { + return XCF_ERROR; + } if( verboseFlag ) { fprintf(stderr,"%dx%d%+d%+d %s %s", spec->layers[i].dim.width, spec->layers[i].dim.height, spec->layers[i].dim.c.l - spec->dim.c.l, spec->layers[i].dim.c.t - spec->dim.c.t, _(showGimpImageType(spec->layers[i].type)), _(showGimpLayerModeEffects(spec->layers[i].mode))); if( spec->layers[i].opacity < 255 ) fprintf(stderr,"/%02d%%",spec->layers[i].opacity * 100 / 255); if( XCF.layers[i].hasMask ) fprintf(stderr,_("/mask")); fprintf(stderr," %s\n",spec->layers[i].name); } } /* Resolve color mode unless we wait until we have the entire image */ if( spec->out_color_mode == COLOR_BY_CONTENTS && !spec->process_in_memory ) { if( guess_callback ) spec->out_color_mode = guess_callback(spec,NULL); if( spec->out_color_mode == COLOR_BY_CONTENTS ) spec->out_color_mode = color_by_layers(spec) ; } + return XCF_OK; } -void +int analyse_colormode(struct FlattenSpec *spec,rgba **allPixels, guesser guess_callback) { unsigned x,y ; int status ; /* 8 - looking for any transparency * 4 - looking for partially transparent pixels * 2 - looking for pixels other than black and white * 1 - looking for colored pixels */ int known_absent = 0 ; int assume_present = 0 ; if( spec->out_color_mode == COLOR_BY_CONTENTS && guess_callback ) spec->out_color_mode = guess_callback(spec,allPixels) ; if( spec->out_color_mode == COLOR_RGB ) assume_present |= 3 ; if( spec->out_color_mode == COLOR_INDEXED ) assume_present |= 3 ; if( spec->out_color_mode == COLOR_GRAY ) assume_present |= 2 ; switch( color_by_layers(spec) ) { case COLOR_GRAY: known_absent |= 1 ; break ; case COLOR_MONO: known_absent |= 3 ; break ; default: break ; } if( spec->partial_transparency_mode == DISSOLVE_PARTIAL_TRANSPARENCY || spec->partial_transparency_mode == PARTIAL_TRANSPARENCY_IMPOSSIBLE ) known_absent |= 4 ; if( ALPHA(spec->default_pixel) >= 128 ) known_absent |= 12 ; else if( spec->default_pixel == FORCE_ALPHA_CHANNEL ) assume_present |= 8 ; status = 15 - (known_absent | assume_present) ; for( y=0; status && ydim.height; y++ ) { rgba *row = allPixels[y] ; if( (status & 3) != 0 ) { /* We're still interested in color */ for( x=0; status && xdim.width; x++ ) { if( NULLALPHA(row[x]) ) status &= ~8 ; else { rgba full = row[x] | (255 << ALPHA_SHIFT) ; if( !FULLALPHA(row[x]) ) status &= ~12 ; if( full == NEWALPHA(0,255) || full == NEWALPHA(-1,255) ) /* Black or white */ ; else if( degrayPixel(row[x]) != -1 ) status &= ~2 ; /* gray */ else status &= ~3 ; /* color */ } } } else { /* Not interested in color */ for( x=0; status && xdim.width; x++ ) { if( NULLALPHA(row[x]) ) status &= ~8 ; else if( !FULLALPHA(row[x]) ) status &= ~12 ; } } } status |= known_absent ; switch( spec->out_color_mode ) { case COLOR_INDEXED: /* The caller takes responsibility */ case COLOR_RGB: /* Everything is fine. */ break ; case COLOR_GRAY: - if( (status & 1) == 0 ) + if( (status & 1) == 0 ) { FatalGeneric(103, _("Grayscale output selected, but colored pixel(s) found")); + return XCF_ERROR; + } break ; case COLOR_MONO: - if( (status & 2) == 0 ) + if( (status & 2) == 0 ) { FatalGeneric(103,_("Monochrome output selected, but not all pixels " "are black or white")); + return XCF_ERROR; + } break ; case COLOR_BY_FILENAME: /* Should not happen ... */ case COLOR_BY_CONTENTS: if( (status & 1) == 0 ) spec->out_color_mode = COLOR_RGB ; else if( (status & 2) == 0 ) spec->out_color_mode = COLOR_GRAY ; else spec->out_color_mode = COLOR_MONO ; break ; } if( (status & 12) == 12 ) /* No transparency found */ spec->default_pixel = NEWALPHA(colormap[0],255); else if( (status & 12) == 4 ) spec->partial_transparency_mode = PARTIAL_TRANSPARENCY_IMPOSSIBLE ; + return XCF_OK; } diff --git a/plugins/impex/xcf/3rdparty/xcftools/flatten.c b/plugins/impex/xcf/3rdparty/xcftools/flatten.c index c021bbe9e8..bfce8bf099 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/flatten.c +++ b/plugins/impex/xcf/3rdparty/xcftools/flatten.c @@ -1,690 +1,725 @@ /* Flattning functions for xcftools * * This file was written by Henning Makholm * It is hereby in the public domain. * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #include "xcftools.h" #include "flatten.h" #include "pixels.h" #include #include #include static rgba __ATTRIBUTE__((noinline,const)) composite_one(rgba bot,rgba top) { unsigned tfrac, alpha ; tfrac = ALPHA(top) ; alpha = 255 ; if( !FULLALPHA(bot) ) { alpha = 255 ^ scaletable[255-ALPHA(bot)][255-ALPHA(top)] ; /* This peculiar combination of ^ and - makes the GCC code * generator for i386 particularly happy. */ tfrac = (256*ALPHA(top) - 1) / alpha ; /* Tfrac is the fraction of the coposited pixel's covered area * that comes from the top pixel. * For mathematical accuracy we ought to scale by 255 and * subtract alpha/2, but this is faster, and never misses the * true value by more than one 1/255. This effect is completely * overshadowed by the linear interpolation in the first place. * (I.e. gamma is ignored when combining intensities). * [In any case, complete fairness is not possible: if the * bottom pixel had alpha=170 and the top has alpha=102, * each should contribute equally to the color of the * resulting alpha=204 pixel, which is not possible in general] * Subtracting one helps the topfrac never be 256, which would * be bad. * On the other hand it means that we would get tfrac=-1 if the * top pixel is completely transparent, and we get a division * by zero if _both_ pixels are fully transparent. These cases * must be handled by all callers. * More snooping in the Gimp sources reveal that it uses * floating-point for its equivalent of tfrac when the * bottom layer has an alpha channel. (alphify() macro * in paint-funcs.c). What gives? */ } return (alpha << ALPHA_SHIFT) + ((uint32_t)scaletable[ tfrac ][255&(top>>RED_SHIFT )] << RED_SHIFT ) + ((uint32_t)scaletable[ tfrac ][255&(top>>GREEN_SHIFT)] << GREEN_SHIFT ) + ((uint32_t)scaletable[ tfrac ][255&(top>>BLUE_SHIFT )] << BLUE_SHIFT ) + ((uint32_t)scaletable[255^tfrac][255&(bot>>RED_SHIFT )] << RED_SHIFT ) + ((uint32_t)scaletable[255^tfrac][255&(bot>>GREEN_SHIFT)] << GREEN_SHIFT ) + ((uint32_t)scaletable[255^tfrac][255&(bot>>BLUE_SHIFT )] << BLUE_SHIFT ) ; } /* merge_normal() takes ownership of bot. * merge_normal() will share ownership of top. * Return: may be shared. */ static struct Tile * __ATTRIBUTE__((noinline)) merge_normal(struct Tile *bot, struct Tile *top) { unsigned i ; assertTileCompatibility(bot,top); /* See if there is an easy winner */ if( (bot->summary & TILESUMMARY_ALLNULL) || (top->summary & TILESUMMARY_ALLFULL) ) { freeTile(bot); return top ; } if( top->summary & TILESUMMARY_ALLNULL ) { freeTile(top); return bot ; } /* Try hard to make top win */ for( i=0; ; i++ ) { if( i == top->count ) { freeTile(bot); return top ; } if( !(NULLALPHA(bot->pixels[i]) || FULLALPHA(top->pixels[i])) ) break ; } INIT_SCALETABLE_IF( !(top->summary & TILESUMMARY_CRISP) ); /* Otherwise bot wins, but is forever changed ... */ if( (top->summary & TILESUMMARY_ALLNULL) == 0 ) { unsigned i ; invalidateSummary(bot,0); for( i=0 ; i < top->count ; i++ ) { if( !NULLALPHA(top->pixels[i]) ) { if( FULLALPHA(top->pixels[i]) || NULLALPHA(bot->pixels[i]) ) bot->pixels[i] = top->pixels[i] ; else bot->pixels[i] = composite_one(bot->pixels[i],top->pixels[i]); } } } freeTile(top); return bot ; } #define exotic_combinator static unsigned __ATTRIBUTE__((const)) exotic_combinator ucombine_ADDITION(uint8_t bot,uint8_t top) { return bot+top > 255 ? 255 : bot+top ; } exotic_combinator ucombine_SUBTRACT(uint8_t bot,uint8_t top) { return top>bot ? 0 : bot-top ; } exotic_combinator ucombine_LIGHTEN_ONLY(uint8_t bot,uint8_t top) { return top > bot ? top : bot ; } exotic_combinator ucombine_DARKEN_ONLY(uint8_t bot,uint8_t top) { return top < bot ? top : bot ; } exotic_combinator ucombine_DIFFERENCE(uint8_t bot,uint8_t top) { return top > bot ? top-bot : bot-top ; } exotic_combinator ucombine_MULTIPLY(uint8_t bot,uint8_t top) { return scaletable[bot][top] ; } exotic_combinator ucombine_DIVIDE(uint8_t bot,uint8_t top) { int result = (int)bot*256 / (1+top) ; return result >= 256 ? 255 : result ; } exotic_combinator ucombine_SCREEN(uint8_t bot,uint8_t top) { /* An inverted version of "multiply" */ return 255 ^ scaletable[255-bot][255-top] ; } exotic_combinator ucombine_OVERLAY(uint8_t bot,uint8_t top) { return scaletable[bot][bot] + 2*scaletable[top][scaletable[bot][255-bot]] ; /* This strange formula is equivalent to * (1-top)*(bot^2) + top*(1-(1-top)^2) * that is, the top value is used to interpolate between * the self-multiply and the self-screen of the bottom. */ /* Note: This is exactly what the "Soft light" effect also * does, though with different code in the Gimp. */ } exotic_combinator ucombine_DODGE(uint8_t bot,uint8_t top) { return ucombine_DIVIDE(bot,255-top); } exotic_combinator ucombine_BURN(uint8_t bot,uint8_t top) { return 255 - ucombine_DIVIDE(255-bot,top); } exotic_combinator ucombine_HARDLIGHT(uint8_t bot,uint8_t top) { if( top >= 128 ) return 255 ^ scaletable[255-bot][2*(255-top)] ; else return scaletable[bot][2*top]; /* The code that implements "hardlight" in Gimp 2.2.10 has some * rounding errors, but this is undoubtedly what is meant. */ } exotic_combinator ucombine_GRAIN_EXTRACT(uint8_t bot,uint8_t top) { int temp = (int)bot - (int)top + 128 ; return temp < 0 ? 0 : temp >= 256 ? 255 : temp ; } exotic_combinator ucombine_GRAIN_MERGE(uint8_t bot,uint8_t top) { int temp = (int)bot + (int)top - 128 ; return temp < 0 ? 0 : temp >= 256 ? 255 : temp ; } struct HSV { enum { HUE_RED_GREEN_BLUE,HUE_RED_BLUE_GREEN,HUE_BLUE_RED_GREEN, HUE_BLUE_GREEN_RED,HUE_GREEN_BLUE_RED,HUE_GREEN_RED_BLUE } hue; unsigned ch1, ch2, ch3 ; }; static void RGBtoHSV(rgba rgb,struct HSV *hsv) { unsigned RED = (uint8_t)(rgb >> RED_SHIFT); unsigned GREEN = (uint8_t)(rgb >> GREEN_SHIFT); unsigned BLUE = (uint8_t)(rgb >> BLUE_SHIFT) ; #define HEXTANT(b,m,t) hsv->ch1 = b, hsv->ch2 = m, hsv->ch3 = t, \ hsv->hue = HUE_ ## b ## _ ## m ## _ ## t if( GREEN <= RED ) if( BLUE <= RED ) if( GREEN <= BLUE ) HEXTANT(GREEN,BLUE,RED); else HEXTANT(BLUE,GREEN,RED); else HEXTANT(GREEN,RED,BLUE); else if( BLUE <= RED ) HEXTANT(BLUE,RED,GREEN); else if( BLUE <= GREEN ) HEXTANT(RED,BLUE,GREEN); else HEXTANT(RED,GREEN,BLUE); #undef HEXTANT } /* merge_exotic() destructively updates bot. * merge_exotic() reads but does not free top. */ -static void __ATTRIBUTE__((noinline)) +static int __ATTRIBUTE__((noinline)) merge_exotic(struct Tile *bot, const struct Tile *top, GimpLayerModeEffects mode) { unsigned i ; assertTileCompatibility(bot,top); - if( (bot->summary & TILESUMMARY_ALLNULL) != 0 ) return ; - if( (top->summary & TILESUMMARY_ALLNULL) != 0 ) return ; + if( (bot->summary & TILESUMMARY_ALLNULL) != 0 ) return XCF_OK; + if( (top->summary & TILESUMMARY_ALLNULL) != 0 ) return XCF_OK; assert( bot->refcount == 1 ); /* The transparency status of bot never changes */ INIT_SCALETABLE_IF(1); for( i=0; i < top->count ; i++ ) { uint32_t RED, GREEN, BLUE ; if( NULLALPHA(bot->pixels[i]) || NULLALPHA(top->pixels[i]) ) continue ; #define UNIFORM(mode) case GIMP_ ## mode ## _MODE: \ RED = ucombine_ ## mode (bot->pixels[i]>>RED_SHIFT , \ top->pixels[i]>>RED_SHIFT ); \ GREEN = ucombine_ ## mode (bot->pixels[i]>>GREEN_SHIFT, \ top->pixels[i]>>GREEN_SHIFT); \ BLUE = ucombine_ ## mode (bot->pixels[i]>>BLUE_SHIFT , \ top->pixels[i]>>BLUE_SHIFT ); \ break ; switch( mode ) { case GIMP_NORMAL_MODE: case GIMP_DISSOLVE_MODE: - FatalUnexpected("Normal and Dissolve mode can't happen here!"); + { + FatalUnexpected("Normal and Dissolve mode can't happen here!"); + return XCF_ERROR; + } UNIFORM(ADDITION); UNIFORM(SUBTRACT); UNIFORM(LIGHTEN_ONLY); UNIFORM(DARKEN_ONLY); UNIFORM(DIFFERENCE); UNIFORM(MULTIPLY); UNIFORM(DIVIDE); UNIFORM(SCREEN); case GIMP_SOFTLIGHT_MODE: /* A synonym for "overlay"! */ UNIFORM(OVERLAY); UNIFORM(DODGE); UNIFORM(BURN); UNIFORM(HARDLIGHT); UNIFORM(GRAIN_EXTRACT); UNIFORM(GRAIN_MERGE); case GIMP_HUE_MODE: case GIMP_SATURATION_MODE: case GIMP_VALUE_MODE: case GIMP_COLOR_MODE: { static struct HSV hsvTop, hsvBot ; RGBtoHSV(top->pixels[i],&hsvTop); if( mode == GIMP_HUE_MODE && hsvTop.ch1 == hsvTop.ch3 ) continue ; RGBtoHSV(bot->pixels[i],&hsvBot); if( mode == GIMP_VALUE_MODE ) { if( hsvBot.ch3 ) { hsvBot.ch1 = (hsvBot.ch1*hsvTop.ch3 + hsvBot.ch3/2) / hsvBot.ch3; hsvBot.ch2 = (hsvBot.ch2*hsvTop.ch3 + hsvBot.ch3/2) / hsvBot.ch3; hsvBot.ch3 = hsvTop.ch3 ; } else { hsvBot.ch1 = hsvBot.ch2 = hsvBot.ch3 = hsvTop.ch3 ; } } else { unsigned mfNum, mfDenom ; if( mode == GIMP_HUE_MODE || mode == GIMP_COLOR_MODE ) { mfNum = hsvTop.ch2-hsvTop.ch1 ; mfDenom = hsvTop.ch3-hsvTop.ch1 ; hsvBot.hue = hsvTop.hue ; } else { mfNum = hsvBot.ch2-hsvBot.ch1 ; mfDenom = hsvBot.ch3-hsvBot.ch1 ; } if( mode == GIMP_SATURATION_MODE ) { if( hsvTop.ch3 == 0 ) hsvBot.ch1 = hsvBot.ch3 ; /* Black has no saturation */ else hsvBot.ch1 = (hsvTop.ch1*hsvBot.ch3 + hsvTop.ch3/2) / hsvTop.ch3; } else if( mode == GIMP_COLOR_MODE ) { /* GIMP_COLOR_MODE works in HSL space instead of HSV. We must * transfer H and S, keeping the L = ch1+ch3 of the bottom pixel, * but the S we transfer works differently from the S in HSV. */ unsigned L = hsvTop.ch1 + hsvTop.ch3 ; unsigned sNum = hsvTop.ch3 - hsvTop.ch1 ; unsigned sDenom = L < 256 ? L : 510-L ; if( sDenom == 0 ) sDenom = 1 ; /* sNum will be 0 */ L = hsvBot.ch1 + hsvBot.ch3 ; if( L < 256 ) { /* Ideally we want to compute L/2 * (1-sNum/sDenom) * But shuffle this a bit so we can use integer arithmetic. * The "-1" in the rounding prevents us from ending up with * ch1 > ch3. */ hsvBot.ch1 = (L*(sDenom-sNum)+sDenom-1)/(2*sDenom); hsvBot.ch3 = L - hsvBot.ch1 ; } else { /* Here our goal is 255 - (510-L)/2 * (1-sNum/sDenom) */ hsvBot.ch3 = 255 - ((510-L)*(sDenom-sNum)+sDenom-1)/(2*sDenom); hsvBot.ch1 = L - hsvBot.ch3 ; } assert(hsvBot.ch3 <= 255); assert(hsvBot.ch3 >= hsvBot.ch1); } if( mfDenom == 0 ) hsvBot.ch2 = hsvBot.ch1 ; else hsvBot.ch2 = hsvBot.ch1 + (mfNum*(hsvBot.ch3-hsvBot.ch1) + mfDenom/2) / mfDenom ; } switch( hsvBot.hue ) { #define HEXTANT(b,m,t) case HUE_ ## b ## _ ## m ## _ ## t : \ b = hsvBot.ch1; m = hsvBot.ch2; t = hsvBot.ch3; break; HEXTANT(RED,GREEN,BLUE); HEXTANT(RED,BLUE,GREEN); HEXTANT(BLUE,RED,GREEN); HEXTANT(BLUE,GREEN,RED); HEXTANT(GREEN,BLUE,RED); HEXTANT(GREEN,RED,BLUE); #undef HEXTANT - default: - FatalUnexpected("Hue hextant is %d", hsvBot.hue); + default: { + + FatalUnexpected("Hue hextant is %d", hsvBot.hue); + return XCF_ERROR; + } } break ; } default: - FatalUnsupportedXCF(_("'%s' layer mode"), + { + FatalUnsupportedXCF(_("'%s' layer mode"), _(showGimpLayerModeEffects(mode))); + return XCF_ERROR; + } } if( FULLALPHA(bot->pixels[i] & top->pixels[i]) ) bot->pixels[i] = (bot->pixels[i] & (255 << ALPHA_SHIFT)) + (RED << RED_SHIFT) + (GREEN << GREEN_SHIFT) + (BLUE << BLUE_SHIFT) ; else { rgba bp = bot->pixels[i] ; /* In a sane world, the alpha of the top pixel would simply be * used to interpolate linearly between the bottom pixel's base * color and the effect-computed color. * But no! What the Gimp actually does is empirically * described by the following (which borrows code from * composite_one() that makes no theoretical sense here): */ unsigned tfrac = ALPHA(top->pixels[i]) ; if( !FULLALPHA(bp) ) { unsigned pseudotop = (tfrac < ALPHA(bp) ? tfrac : ALPHA(bp)); unsigned alpha = 255 ^ scaletable[255-ALPHA(bp)][255-pseudotop] ; tfrac = (256*pseudotop - 1) / alpha ; } bot->pixels[i] = (bp & (255 << ALPHA_SHIFT)) + ((rgba)scaletable[ tfrac ][ RED ] << RED_SHIFT ) + ((rgba)scaletable[ tfrac ][ GREEN ] << GREEN_SHIFT) + ((rgba)scaletable[ tfrac ][ BLUE ] << BLUE_SHIFT ) + ((rgba)scaletable[255^tfrac][255&(bp>>RED_SHIFT )] << RED_SHIFT ) + ((rgba)scaletable[255^tfrac][255&(bp>>GREEN_SHIFT)] << GREEN_SHIFT) + ((rgba)scaletable[255^tfrac][255&(bp>>BLUE_SHIFT )] << BLUE_SHIFT ) ; } } - return ; + return XCF_OK; } static void dissolveTile(struct Tile *tile) { unsigned i ; summary_t summary ; assert( tile->refcount == 1 ); if( (tile->summary & TILESUMMARY_CRISP) ) return ; summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLNULL + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; for( i = 0 ; i < tile->count ; i++ ) { if( FULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLNULL ; else if ( NULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLFULL ; else if( ALPHA(tile->pixels[i]) > rand() % 0xFF ) { tile->pixels[i] |= 255 << ALPHA_SHIFT ; summary &= ~TILESUMMARY_ALLNULL ; } else { tile->pixels[i] = 0 ; summary &= ~TILESUMMARY_ALLFULL ; } } tile->summary = summary ; } static void roundAlpha(struct Tile *tile) { unsigned i ; summary_t summary ; assert( tile->refcount == 1 ); if( (tile->summary & TILESUMMARY_CRISP) ) return ; summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLNULL + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; for( i = 0 ; i < tile->count ; i++ ) { if( ALPHA(tile->pixels[i]) >= 128 ) { tile->pixels[i] |= 255 << ALPHA_SHIFT ; summary &= ~TILESUMMARY_ALLNULL ; } else { tile->pixels[i] = 0 ; summary &= ~TILESUMMARY_ALLFULL ; } } tile->summary = summary ; } /* flattenTopdown() shares ownership of top. * The return value may be a shared tile. */ static struct Tile * flattenTopdown(struct FlattenSpec *spec, struct Tile *top, unsigned nlayers, const struct rect *where) { struct Tile *tile; while( nlayers-- ) { if( tileSummary(top) & TILESUMMARY_ALLFULL ) return top ; if( !spec->layers[nlayers].isVisible ) continue ; tile = getLayerTile(&spec->layers[nlayers],where); + if (tile == XCF_PTR_EMPTY) { + return XCF_PTR_EMPTY; + } if( tile->summary & TILESUMMARY_ALLNULL ) continue ; /* Simulate a tail call */ switch( spec->layers[nlayers].mode ) { case GIMP_NORMAL_NOPARTIAL_MODE: roundAlpha(tile) ; /* Falls through */ case GIMP_DISSOLVE_MODE: dissolveTile(tile); /* Falls through */ case GIMP_NORMAL_MODE: top = merge_normal(tile,top); break ; default: { struct Tile *below, *above ; unsigned i ; if( !(top->summary & TILESUMMARY_ALLNULL) ) { rgba tile_or = 0 ; invalidateSummary(tile,0); for( i=0; icount; i++ ) if( FULLALPHA(top->pixels[i]) ) tile->pixels[i] = 0 ; else tile_or |= tile->pixels[i] ; /* If the tile only has pixels that will be covered by 'top' anyway, * forget it anyway. */ if( ALPHA(tile_or) == 0 ) { freeTile(tile); break ; /* from the switch, which will continue the while */ } } /* Create a dummy top for the layers below this */ if( top->summary & TILESUMMARY_CRISP ) { above = forkTile(top); + if(above == XCF_PTR_EMPTY) { + return XCF_PTR_EMPTY; + } } else { summary_t summary = TILESUMMARY_ALLNULL ; above = newTile(*where); for( i=0; icount; i++ ) if( FULLALPHA(top->pixels[i]) ) { above->pixels[i] = -1 ; summary = 0 ; } else above->pixels[i] = 0 ; above->summary = TILESUMMARY_UPTODATE + TILESUMMARY_CRISP + summary; } below = flattenTopdown(spec, above, nlayers, where); + if (below == XCF_PTR_EMPTY) { + return XCF_PTR_EMPTY; + } if( below->refcount > 1 ) { - assert( below == top ); + if (below != top) { + return XCF_PTR_EMPTY; + } /* This can only happen if 'below' is a copy of 'top' * THROUGH 'above', which in turn means that none of all * this is visible after all. So just free it and return 'top'. */ freeTile(below); return top ; } - merge_exotic(below,tile,spec->layers[nlayers].mode); + if (merge_exotic(below,tile,spec->layers[nlayers].mode) != XCF_OK) { + return XCF_PTR_EMPTY; + } freeTile(tile); top = merge_normal(below,top); return top ; } } } return top ; } -static void +static int addBackground(struct FlattenSpec *spec, struct Tile *tile, unsigned ncols) { unsigned i ; if( tileSummary(tile) & TILESUMMARY_ALLFULL ) - return ; + return XCF_OK; switch( spec->partial_transparency_mode ) { case FORBID_PARTIAL_TRANSPARENCY: - if( !(tileSummary(tile) & TILESUMMARY_CRISP) ) + if( !(tileSummary(tile) & TILESUMMARY_CRISP) ) { FatalGeneric(102,_("Flattened image has partially transparent pixels")); + return XCF_ERROR; + } break ; case DISSOLVE_PARTIAL_TRANSPARENCY: dissolveTile(tile); break ; case ALLOW_PARTIAL_TRANSPARENCY: case PARTIAL_TRANSPARENCY_IMPOSSIBLE: break ; } if( spec->default_pixel == CHECKERED_BACKGROUND ) { INIT_SCALETABLE_IF( !(tile->summary & TILESUMMARY_CRISP ) ); for( i=0; icount; i++ ) if( !FULLALPHA(tile->pixels[i]) ) { rgba fillwith = ((i/ncols)^(i%ncols))&8 ? 0x66 : 0x99 ; fillwith = graytable[fillwith] + (255 << ALPHA_SHIFT) ; if( NULLALPHA(tile->pixels[i]) ) tile->pixels[i] = fillwith ; else tile->pixels[i] = composite_one(fillwith,tile->pixels[i]); } tile->summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; - return ; + return XCF_OK; } - if( !FULLALPHA(spec->default_pixel) ) return ; + if( !FULLALPHA(spec->default_pixel) ) return XCF_OK; if( tileSummary(tile) & TILESUMMARY_ALLNULL ) { fillTile(tile,spec->default_pixel); } else { INIT_SCALETABLE_IF( !(tile->summary & TILESUMMARY_CRISP) ); for( i=0; icount; i++ ) if( NULLALPHA(tile->pixels[i]) ) tile->pixels[i] = spec->default_pixel ; else if( FULLALPHA(tile->pixels[i]) ) ; else tile->pixels[i] = composite_one(spec->default_pixel,tile->pixels[i]); tile->summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; } + return XCF_OK; } -void +int flattenIncrementally(struct FlattenSpec *spec,lineCallback callback) { rgba *rows[TILE_HEIGHT] ; unsigned i, y, nrows, ncols ; struct rect where ; struct Tile *tile ; static struct Tile toptile ; toptile.count = TILE_HEIGHT * TILE_WIDTH ; fillTile(&toptile,0); for( where.t = spec->dim.c.t; where.t < spec->dim.c.b; where.t=where.b ) { where.b = TILE_TOP(where.t)+TILE_HEIGHT ; if( where.b > spec->dim.c.b ) where.b = spec->dim.c.b ; nrows = where.b - where.t ; for( y = 0; y < nrows ; y++ ) rows[y] = xcfmalloc(4*(spec->dim.c.r-spec->dim.c.l)); for( where.l = spec->dim.c.l; where.l < spec->dim.c.r; where.l=where.r ) { where.r = TILE_LEFT(where.l)+TILE_WIDTH ; if( where.r > spec->dim.c.r ) where.r = spec->dim.c.r ; ncols = where.r - where.l ; toptile.count = ncols * nrows ; toptile.refcount = 2 ; /* For bug checking */ assert( toptile.summary == TILESUMMARY_UPTODATE + TILESUMMARY_ALLNULL + TILESUMMARY_CRISP ); tile = flattenTopdown(spec,&toptile,spec->numLayers,&where) ; + if (tile == XCF_PTR_EMPTY) { + return XCF_ERROR; + } toptile.refcount-- ; /* addBackground may change destructively */ - addBackground(spec,tile,ncols); + if (addBackground(spec,tile,ncols) != XCF_OK) { + return XCF_ERROR; + } for( i = 0 ; i < tile->count ; i++ ) if( NULLALPHA(tile->pixels[i]) ) tile->pixels[i] = 0 ; for( y = 0 ; y < nrows ; y++ ) memcpy(rows[y] + (where.l - spec->dim.c.l), tile->pixels + y * ncols, ncols*4); if( tile == &toptile ) { fillTile(&toptile,0); } else { freeTile(tile); } } for( y = 0 ; y < nrows ; y++ ) callback(spec->dim.width,rows[y]); } + return XCF_OK; } static rgba **collectPointer ; static void collector(unsigned num,rgba *row) { num += 0; *collectPointer++ = row ; } rgba ** flattenAll(struct FlattenSpec *spec) { rgba **rows = xcfmalloc(spec->dim.height * sizeof(rgba*)); if( verboseFlag ) fprintf(stderr,_("Flattening image ...")); collectPointer = rows ; - flattenIncrementally(spec,collector); + if (flattenIncrementally(spec,collector) != XCF_OK) { + xcffree(rows); + collectPointer = XCF_PTR_EMPTY; + return XCF_PTR_EMPTY; + } if( verboseFlag ) fprintf(stderr,"\n"); return rows ; } void shipoutWithCallback(struct FlattenSpec *spec, rgba **pixels, lineCallback callback) { unsigned i ; for( i = 0; i < spec->dim.height; i++ ) { callback(spec->dim.width,pixels[i]); } xcffree(pixels); } diff --git a/plugins/impex/xcf/3rdparty/xcftools/flatten.h b/plugins/impex/xcf/3rdparty/xcftools/flatten.h index 2047cbca6d..672afaa39b 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/flatten.h +++ b/plugins/impex/xcf/3rdparty/xcftools/flatten.h @@ -1,77 +1,77 @@ /* Flattning functions for xcftools * * This file was written by Henning Makholm * It is hereby in the public domain. * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #ifndef FLATTEN_H #define FLATTEN_H #include "xcftools.h" #include "pixels.h" #define PERHAPS_ALPHA_CHANNEL (NEWALPHA(0,1)) #define FORCE_ALPHA_CHANNEL (NEWALPHA(0,2)) #define CHECKERED_BACKGROUND (NEWALPHA(0,200)) struct FlattenSpec { struct tileDimensions dim ; rgba default_pixel ; int numLayers ; struct xcfLayer *layers ; const char * transmap_filename ; const char * output_filename ; enum out_color_mode { COLOR_BY_FILENAME, COLOR_BY_CONTENTS, COLOR_INDEXED, COLOR_RGB, COLOR_GRAY, COLOR_MONO } out_color_mode ; enum { ALLOW_PARTIAL_TRANSPARENCY, DISSOLVE_PARTIAL_TRANSPARENCY, FORBID_PARTIAL_TRANSPARENCY, PARTIAL_TRANSPARENCY_IMPOSSIBLE } partial_transparency_mode ; enum { USE_CANVAS = 0, MANUAL_OFFSET = 1, MANUAL_CROP = 2, AUTOCROP = 4 } window_mode ; int process_in_memory ; int gimpish_indexed ; }; /* From flatspec.c */ void init_flatspec(struct FlattenSpec *); -void add_layer_request(struct FlattenSpec *,const char *name); +int add_layer_request(struct FlattenSpec *,const char *name); struct xcfLayer *lastlayerspec(struct FlattenSpec *,const char *option); typedef enum out_color_mode (*guesser) (struct FlattenSpec *,rgba **); /* Call this after processing options, and after opening the XCF file */ -void complete_flatspec(struct FlattenSpec *,guesser); -void analyse_colormode(struct FlattenSpec *,rgba **allPixels,guesser); +int complete_flatspec(struct FlattenSpec *,guesser); +int analyse_colormode(struct FlattenSpec *,rgba **allPixels,guesser); /* From flatten.c */ typedef void (*lineCallback)(unsigned num,rgba *pixels); -void flattenIncrementally(struct FlattenSpec *,lineCallback); +int flattenIncrementally(struct FlattenSpec *,lineCallback); rgba **flattenAll(struct FlattenSpec*); void shipoutWithCallback(struct FlattenSpec *,rgba **pixels,lineCallback); #endif /* FLATTEN_H */ diff --git a/plugins/impex/xcf/3rdparty/xcftools/pixels.c b/plugins/impex/xcf/3rdparty/xcftools/pixels.c index d7037937c9..51999520f2 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/pixels.c +++ b/plugins/impex/xcf/3rdparty/xcftools/pixels.c @@ -1,491 +1,583 @@ /* Pixel and tile functions for xcftools * * This file was written by Henning Makholm * It is hereby in the public domain. * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #define DEBUG #include "xcftools.h" #include "pixels.h" #include #include rgba colormap[256] ; unsigned colormapLength=0 ; int degrayPixel(rgba pixel) { if( ((pixel >> RED_SHIFT) & 255) == ((pixel >> GREEN_SHIFT) & 255) && ((pixel >> RED_SHIFT) & 255) == ((pixel >> BLUE_SHIFT) & 255) ) return (pixel >> RED_SHIFT) & 255 ; return -1 ; } /* ****************************************************************** */ typedef const struct _convertParams { int bpp ; int shift[4] ; uint32_t base_pixel ; const rgba *lookup ; } convertParams ; #define RGB_SHIFT RED_SHIFT, GREEN_SHIFT, BLUE_SHIFT #define OPAQUE (255 << ALPHA_SHIFT) static convertParams convertRGB = { 3, {RGB_SHIFT}, OPAQUE, 0 }; static convertParams convertRGBA = { 4, {RGB_SHIFT, ALPHA_SHIFT}, 0,0 }; static convertParams convertGRAY = { 1, {-1}, OPAQUE, graytable }; static convertParams convertGRAYA = { 2, {-1,ALPHA_SHIFT}, 0, graytable }; static convertParams convertINDEXED = { 1, {-1}, OPAQUE, colormap }; static convertParams convertINDEXEDA = { 2, {-1,ALPHA_SHIFT}, 0, colormap }; static convertParams convertColormap = { 3, {RGB_SHIFT}, 0, 0 }; static convertParams convertChannel = { 1, {ALPHA_SHIFT}, 0, 0 }; /* ****************************************************************** */ static int -tileDirectoryOneLevel(struct tileDimensions *dim,uint32_t ptr) +tileDirectoryOneLevel(struct tileDimensions *dim,uint32_t ptr, int* ptrOut) { - if( ptr == 0 ) - return 0 ; + if( ptr == 0 ) { + *ptrOut = 0; + return XCF_OK; /* allowed by xcf, apparently */ + } if( xcfL(ptr ) != dim->c.r - dim->c.l || - xcfL(ptr+4) != dim->c.b - dim->c.t ) + xcfL(ptr+4) != dim->c.b - dim->c.t ) { FatalBadXCF("Drawable size mismatch at %" PRIX32, ptr); - return ptr += 8 ; + *ptrOut = XCF_PTR_EMPTY; + return XCF_ERROR; + } + *ptrOut = (ptr += 8) ; + return XCF_OK; } -static void +static int initTileDirectory(struct tileDimensions *dim,struct xcfTiles *tiles, const char *type) { uint32_t ptr ; uint32_t data ; ptr = tiles->hierarchy ; tiles->hierarchy = 0 ; - if( (ptr = tileDirectoryOneLevel(dim,ptr)) == 0 ) return ; + int ptrOut; + if (tileDirectoryOneLevel(dim,ptr, &ptrOut) != XCF_OK) { + return XCF_ERROR; + } + if (ptrOut == XCF_PTR_EMPTY) { + return XCF_OK; + } + ptr = ptrOut; if( tiles->params == &convertChannel ) { /* A layer mask is a channel. * Skip a name and a property list. */ - xcfString(ptr,&ptr); - while( xcfNextprop(&ptr,&data) != PROP_END ) - ; - ptr = xcfOffset(ptr,4*4); - if( (ptr = tileDirectoryOneLevel(dim,ptr)) == 0 ) return ; + xcfString(ptr,&ptr); + PropType type; + int response; + while( (response = xcfNextprop(&ptr,&data, &type)) != XCF_ERROR && type != PROP_END ) { + + } + if (response != XCF_OK) { + return XCF_ERROR; + } + uint32_t ptrout; + if(xcfOffset(ptr,4*4, &ptrout) != XCF_OK) return XCF_ERROR; + ptr = ptrout; + if (tileDirectoryOneLevel(dim,ptr, &ptrOut) != XCF_OK) { + return XCF_ERROR; + } + if (ptrOut == XCF_PTR_EMPTY) { + return XCF_OK; + } + ptr = ptrOut; } /* The XCF format has a dummy "hierarchy" level which was * once meant to mean something, but never happened. It contains * the bpp value and a list of "level" pointers; but only the * first level actually contains data. */ data = xcfL(ptr) ; - if( xcfL(ptr) != tiles->params->bpp ) + if( xcfL(ptr) != tiles->params->bpp ) { FatalBadXCF("%"PRIu32" bytes per pixel for %s drawable",xcfL(ptr),type); - ptr = xcfOffset(ptr+4,3*4) ; - if( (ptr = tileDirectoryOneLevel(dim,ptr)) == 0 ) return ; + return XCF_ERROR; + } + uint32_t ptrout; + if(xcfOffset(ptr+4,3*4, &ptrout) != XCF_OK) return XCF_ERROR; + ptr = ptrout; + if (tileDirectoryOneLevel(dim,ptr, &ptrOut) != XCF_OK) { + return XCF_ERROR; + } + if (ptrOut == XCF_PTR_EMPTY) { + return XCF_OK; + } + ptr = ptrOut; - xcfCheckspace(ptr,dim->ntiles*4+4,"Tile directory at %" PRIX32,ptr); + if (xcfCheckspace(ptr,dim->ntiles*4+4,"Tile directory at %" PRIX32,ptr) != XCF_OK) { + return XCF_ERROR; + } /* if( xcfL(ptr + dim->ntiles*4) != 0 ) FatalBadXCF("Wrong sized tile directory at %" PRIX32,ptr);*/ #define REUSE_RAW_DATA tiles->tileptrs = (uint32_t*)(xcf_file + ptr) #if defined(WORDS_BIGENDIAN) && defined(CAN_DO_UNALIGNED_WORDS) REUSE_RAW_DATA; #else # if defined(WORDS_BIGENDIAN) if( (ptr&3) == 0 ) REUSE_RAW_DATA; else # endif { unsigned i ; tiles->tileptrs = xcfmalloc(dim->ntiles * sizeof(uint32_t)) ; for( i = 0 ; i < dim->ntiles ; i++ ) tiles->tileptrs[i] = xcfL(ptr+i*4); } #endif + return XCF_OK; } -void +int initLayer(struct xcfLayer *layer) { if( layer->dim.ntiles == 0 || (layer->pixels.hierarchy == 0 && layer->mask.hierarchy == 0) ) - return ; + return XCF_OK; switch(layer->type) { #define DEF(X) case GIMP_##X##_IMAGE: layer->pixels.params = &convert##X; break DEF(RGB); DEF(RGBA); DEF(GRAY); DEF(GRAYA); DEF(INDEXED); DEF(INDEXEDA); default: - FatalUnsupportedXCF(_("Layer type %s"),_(showGimpImageType(layer->type))); + { + FatalUnsupportedXCF(_("Layer type %s"),_(showGimpImageType(layer->type))); + return XCF_ERROR; + } + + } + if (initTileDirectory(&layer->dim,&layer->pixels, + _(showGimpImageType(layer->type))) != XCF_OK) { + return XCF_ERROR; } - initTileDirectory(&layer->dim,&layer->pixels, - _(showGimpImageType(layer->type))); layer->mask.params = &convertChannel ; - initTileDirectory(&layer->dim,&layer->mask,"layer mask"); + if (initTileDirectory(&layer->dim,&layer->mask,"layer mask") != XCF_OK) { + return XCF_ERROR; + } + return XCF_OK; } -static void copyStraightPixels(rgba *dest,unsigned npixels, +static int copyStraightPixels(rgba *dest,unsigned npixels, uint32_t ptr,convertParams *params); -void +int initColormap(void) { uint32_t ncolors ; if( XCF.colormapptr == 0 ) { colormapLength = 0 ; - return ; + return XCF_OK; } ncolors = xcfL(XCF.colormapptr) ; - if( ncolors > 256 ) + if( ncolors > 256 ) { FatalUnsupportedXCF(_("Color map has more than 256 entries")); - copyStraightPixels(colormap,ncolors,XCF.colormapptr+4,&convertColormap); + return XCF_ERROR; + } + if(copyStraightPixels(colormap,ncolors,XCF.colormapptr+4,&convertColormap) != XCF_OK) { + return XCF_ERROR; + } colormapLength = ncolors ; #ifdef xDEBUG { unsigned j ; fprintf(stderr,"Colormap decoding OK\n"); for( j = 0 ; j < ncolors ; j++ ) { if( j % 8 == 0 ) fprintf(stderr,"\n"); fprintf(stderr," %08x",colormap[j]); } fprintf(stderr,"\n"); } #endif + return XCF_OK; } /* ****************************************************************** */ struct Tile * newTile(struct rect r) { unsigned npixels = (unsigned)(r.b-r.t) * (unsigned)(r.r-r.l) ; struct Tile *data = xcfmalloc(sizeof(struct Tile) - sizeof(rgba)*(TILE_HEIGHT*TILE_WIDTH - npixels)) ; data->count = npixels ; data->refcount = 1 ; data->summary = 0 ; return data ; } struct Tile * forkTile(struct Tile* tile) { - if( ++tile->refcount <= 0 ) + if( ++tile->refcount <= 0 ) { FatalUnsupportedXCF(_("Unbelievably many layers?\n" "More likely to be a bug in %s"),progname); + return XCF_PTR_EMPTY; + } return tile ; } void freeTile(struct Tile* tile) { if( --tile->refcount == 0 ) xcffree(tile) ; } summary_t tileSummary(struct Tile *tile) { unsigned i ; summary_t summary ; if( (tile->summary & TILESUMMARY_UPTODATE) != 0 ) return tile->summary ; summary = TILESUMMARY_ALLNULL + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; for( i=0; summary && icount; i++ ) { if( FULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLNULL ; else if( NULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLFULL ; else summary = 0 ; } summary += TILESUMMARY_UPTODATE ; tile->summary = summary ; return summary ; } void fillTile(struct Tile *tile,rgba data) { unsigned i ; for( i = 0 ; i < tile->count ; i++ ) tile->pixels[i] = data ; if( FULLALPHA(data) ) tile->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLFULL+TILESUMMARY_CRISP; else if (NULLALPHA(data) ) tile->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLNULL+TILESUMMARY_CRISP; else tile->summary = TILESUMMARY_UPTODATE ; } /* ****************************************************************** */ -static void +static int copyStraightPixels(rgba *dest,unsigned npixels, uint32_t ptr,convertParams *params) { unsigned bpp = params->bpp; const rgba *lookup = params->lookup; rgba base_pixel = params->base_pixel ; uint8_t *bp = xcf_file + ptr ; - xcfCheckspace(ptr,bpp*npixels, - "pixel array (%u x %d bpp) at %"PRIX32,npixels,bpp,ptr); + int response; + if ((response = xcfCheckspace(ptr,bpp*npixels, + "pixel array (%u x %d bpp) at %"PRIX32,npixels,bpp,ptr)) != XCF_OK) { + return XCF_ERROR; + } while( npixels-- ) { rgba pixel = base_pixel ; unsigned i ; for( i = 0 ; i < bpp ; i++ ) { if( params->shift[i] < 0 ) { pixel += lookup[*bp++] ; } else { pixel += *bp++ << params->shift[i] ; } } *dest++ = pixel ; } + return XCF_OK; } -static void +static int copyRLEpixels(rgba *dest,unsigned npixels,uint32_t ptr,convertParams *params) { unsigned i,j ; rgba base_pixel = params->base_pixel ; #ifdef xDEBUG fprintf(stderr,"RLE stream at %x, want %u x %u pixels, base %x\n", ptr,params->bpp,npixels,base_pixel); #endif - /* This algorithm depends on the indexed byte always being the first one */ if( params->shift[0] < -1 ) base_pixel = 0 ; for( j = npixels ; j-- ; ) dest[j] = base_pixel ; for( i = 0 ; i < params->bpp ; i++ ) { int shift = params->shift[i] ; if( shift < 0 ) shift = 0 ; for( j = 0 ; j < npixels ; ) { int countspec ; unsigned count ; - xcfCheckspace(ptr,2,"RLE data stream"); + if (xcfCheckspace(ptr,2,"RLE data stream") != XCF_OK) { + return XCF_ERROR; + } countspec = (int8_t) xcf_file[ptr++] ; count = countspec >= 0 ? countspec+1 : -countspec ; if( count == 128 ) { - xcfCheckspace(ptr,3,"RLE long count"); + if (xcfCheckspace(ptr,3,"RLE long count") != XCF_OK) { + return XCF_ERROR; + } count = xcf_file[ptr++] << 8 ; count += xcf_file[ptr++] ; } - if( j + count > npixels ) + if( j + count > npixels ) { FatalBadXCF("Overlong RLE run at %"PRIX32" (plane %u, %u left)", ptr,i,npixels-j); + return XCF_ERROR; + } if( countspec >= 0 ) { rgba data = (uint32_t) xcf_file[ptr++] << shift ; while( count-- ) dest[j++] += data ; } else { while( count-- ) dest[j++] += (uint32_t) xcf_file[ptr++] << shift ; } } if( i == 0 && params->shift[0] < 0 ) { const rgba *lookup = params->lookup ; base_pixel = params->base_pixel ; for( j = npixels ; j-- ; ) { dest[j] = lookup[dest[j]-base_pixel] + base_pixel ; } } } #ifdef xDEBUG fprintf(stderr,"RLE decoding OK at %"PRIX32"\n",ptr); /* for( j = 0 ; j < npixels ; j++ ) { if( j % 8 == 0 ) fprintf(stderr,"\n"); fprintf(stderr," %8x",dest[j]); } fprintf(stderr,"\n"); */ #endif + return XCF_OK; } -static void +static int copyTilePixels(struct Tile *dest, uint32_t ptr,convertParams *params) { if( FULLALPHA(params->base_pixel) ) dest->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLFULL+TILESUMMARY_CRISP; else dest->summary = 0 ; switch( XCF.compression ) { case COMPRESS_NONE: - copyStraightPixels(dest->pixels,dest->count,ptr,params); + if (copyStraightPixels(dest->pixels,dest->count,ptr,params) != XCF_OK) { + return XCF_ERROR; + } break ; case COMPRESS_RLE: - copyRLEpixels(dest->pixels,dest->count,ptr,params); + if (copyRLEpixels(dest->pixels,dest->count,ptr,params) != XCF_OK) { + return XCF_ERROR; + } break ; default: - FatalUnsupportedXCF(_("%s compression"), + { + FatalUnsupportedXCF(_("%s compression"), _(showXcfCompressionType(XCF.compression))); + return XCF_ERROR; + } } + return XCF_OK; } struct Tile * getMaskOrLayerTile(struct tileDimensions *dim, struct xcfTiles *tiles, struct rect want) { struct Tile *tile = newTile(want); - assert( want.l < want.r && want.t < want.b ); + if (want.l >= want.r || want.t >= want.b ) { + freeTile(tile); + return XCF_PTR_EMPTY; + } + + if( tiles->tileptrs == 0 ) { fillTile(tile,0); return tile ; } #ifdef xDEBUG fprintf(stderr,"getMaskOrLayer: (%d-%d),(%d-%d)\n",left,right,top,bottom); #endif if( isSubrect(want,dim->c) && (want.l - dim->c.l) % TILE_WIDTH == 0 && (want.t - dim->c.t) % TILE_HEIGHT == 0 ) { int tx = TILE_NUM(want.l - dim->c.l); int ty = TILE_NUM(want.t - dim->c.t); if( want.r == TILEXn(*dim,tx+1) && want.b == TILEYn(*dim,ty+1) ) { /* The common case? An entire single tile from the layer */ - copyTilePixels(tile,tiles->tileptrs[tx + ty*dim->tilesx],tiles->params); + if (copyTilePixels(tile,tiles->tileptrs[tx + ty*dim->tilesx],tiles->params) != XCF_OK) { + freeTile(tile); + return XCF_PTR_EMPTY; + } return tile ; } } /* OK, we must construct the wanted tile as a jigsaw */ { unsigned width = want.r-want.l ; rgba *pixvert = tile->pixels ; rgba *pixhoriz ; int y, ty, l0, l1 ; int x, tx, c0, c1 ; unsigned lstart, lnum ; unsigned cstart, cnum ; if( !isSubrect(want,dim->c) ) { if( want.l < dim->c.l ) pixvert += (dim->c.l - want.l), want.l = dim->c.l ; if( want.r > dim->c.r ) want.r = dim->c.r ; if( want.t < dim->c.t ) pixvert += (dim->c.t - want.t) * width, want.t = dim->c.t ; if( want.b > dim->c.b ) want.b = dim->c.b ; fillTile(tile,0); } else { tile->summary = -1 ; /* I.e. whatever the jigsaw pieces say */ } #ifdef xDEBUG fprintf(stderr,"jig0 (%d-%d),(%d-%d)\n",left,right,top,bottom); #endif for( y=want.t, ty=TILE_NUM(want.t-dim->c.t), l0=TILEYn(*dim,ty); y want.b ? want.b : l1) - y ; pixhoriz = pixvert ; for( x=want.l, tx=TILE_NUM(want.l-dim->c.l), c0=TILEXn(*dim,tx); x want.r ? want.r : c1) - x ; { static struct Tile tmptile ; unsigned dwidth = c1-c0 ; unsigned i, j ; tmptile.count = (c1-c0)*(l1-l0) ; #ifdef xDEBUG fprintf(stderr,"jig ty=%u(%u-%u-%u)(%u+%u) tx=%u(%u-%u-%u)(%u+%u)\n", ty,l0,y,l1,lstart,lnum, tx,c0,x,c1,cstart,cnum); #endif - copyTilePixels(&tmptile, - tiles->tileptrs[tx+ty*dim->tilesx],tiles->params); + if (copyTilePixels(&tmptile, + tiles->tileptrs[tx+ty*dim->tilesx],tiles->params) != XCF_OK) { + freeTile(tile); + return XCF_PTR_EMPTY; + } + for(i=0; isummary &= tmptile.summary ; } } } } return tile ; } void applyMask(struct Tile *tile, struct Tile *mask) { unsigned i ; assertTileCompatibility(tile,mask); assert( tile->count == mask->count ); INIT_SCALETABLE_IF(1); invalidateSummary(tile,0); for( i=0; i < tile->count ;i++ ) tile->pixels[i] = NEWALPHA(tile->pixels[i], scaletable[mask->pixels[i]>>ALPHA_SHIFT] [ALPHA(tile->pixels[i])]); freeTile(mask); } struct Tile * getLayerTile(struct xcfLayer *layer,const struct rect *where) { struct Tile *data ; #ifdef xDEBUG fprintf(stderr,"getLayerTile(%s): (%d-%d),(%d-%d)\n", layer->name,where->l,where->r,where->t,where->b); #endif if( disjointRects(*where,layer->dim.c) || layer->opacity == 0 ) { data = newTile(*where); fillTile(data,0); return data ; } data = getMaskOrLayerTile(&layer->dim,&layer->pixels,*where); + if (data == XCF_PTR_EMPTY) { + return XCF_PTR_EMPTY; + } if( (data->summary & TILESUMMARY_ALLNULL) != 0 ) return data ; if( layer->hasMask ) { struct Tile *mask = getMaskOrLayerTile(&layer->dim,&layer->mask,*where); + if (mask == XCF_PTR_EMPTY) { /* error */ + return XCF_PTR_EMPTY; + } applyMask(data,mask); } if( layer->opacity < 255 ) { const uint8_t *ourtable ; int i ; invalidateSummary(data,~(TILESUMMARY_CRISP | TILESUMMARY_ALLFULL)); INIT_SCALETABLE_IF(1); ourtable = scaletable[layer->opacity] ; for( i=0; i < data->count; i++ ) data->pixels[i] = NEWALPHA(data->pixels[i],ourtable[ALPHA(data->pixels[i])]) ; } return data ; } diff --git a/plugins/impex/xcf/3rdparty/xcftools/pixels.h b/plugins/impex/xcf/3rdparty/xcftools/pixels.h index 5342351367..6351784b3c 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/pixels.h +++ b/plugins/impex/xcf/3rdparty/xcftools/pixels.h @@ -1,130 +1,130 @@ /* Pixel and tile functions for xcftools * * This file was written by Henning Makholm * It is hereby in the public domain. * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #ifndef PIXELS_H #define PIXELS_H #include "xcftools.h" /* MACROS FOR INTERNAL PIXEL ORDERING HERE */ /*=========================================*/ /* In principle the internal representation of pixels may change. * - this was supposed to allow an optimization where a layer could * be represented as a pointer into the mmapped xcf file, if * alignment, bpp, and endianness agreed (the point was that the * pixel representation had to agree with the endianness). * * However, it turns out that the current Gimp _always_ saves images * with RLE encoding of tiles, so such an effort would be in vain. * * Just for modularity, nevertheless try to isolate knowledge of * the RGBA-to-machine-word packing in this section of the * header file. Define new macros if necessary. * * Given that we don't have to agree with the uncompressed * RLE format, we choose to have the alpha in the _least_ * significant byte on all archs - it is tested and used more * often than the visible channels. */ typedef uint32_t rgba ; #define ALPHA_SHIFT 0 #define RED_SHIFT 8 #define GREEN_SHIFT 16 #define BLUE_SHIFT 24 #define ALPHA(rgba) ((uint8_t)(rgba)) #define FULLALPHA(rgba) ((uint8_t)(rgba) == 255) #define NULLALPHA(rgba) ((uint8_t)(rgba) == 0) #define NEWALPHA(rgb,a) (((rgba)(rgb) & 0xFFFFFF00) + (a)) #ifdef PRECOMPUTED_SCALETABLE extern const uint8_t scaletable[256][256] ; #define INIT_SCALETABLE_IF(foo) ((void)0) #else extern uint8_t scaletable[256][256] ; extern int ok_scaletable ; void mk_scaletable(void); #define INIT_SCALETABLE_IF(foo) \ (ok_scaletable || !(foo) || (mk_scaletable(),0) ) #endif extern const rgba graytable[256] ; extern rgba colormap[256] ; extern unsigned colormapLength ; -void initLayer(struct xcfLayer *); -void initColormap(); +int initLayer(struct xcfLayer *); +int initColormap(); int degrayPixel(rgba); /* returns -1 for non-gray pixels */ /* ******************************************************* */ #define TILEXn(dim,tx) \ ((tx)==(dim).tilesx ? (dim).c.r : (dim).c.l + ((tx)*TILE_WIDTH)) #define TILEYn(dim,ty) \ ((ty)==(dim).tilesy ? (dim).c.b : (dim).c.t + ((ty)*TILE_HEIGHT)) #if defined(__i386__) /* This is probably the only common architecture where small constants * are more efficient for byte operations. */ typedef int8_t summary_t ; typedef short int refcount_t ; #else typedef int summary_t ; typedef int refcount_t ; #endif #define TILESUMMARY_UPTODATE 8 #define TILESUMMARY_ALLNULL 4 #define TILESUMMARY_ALLFULL 2 #define TILESUMMARY_CRISP 1 /* everything either null or full */ struct Tile { refcount_t refcount ; summary_t summary ; /* a combination of TIMESUMMARY_FOO constatns */ unsigned count ; rgba pixels[TILE_WIDTH * TILE_HEIGHT]; }; /* Actually, the Tile structures that get allocated many not have * room for that many pixels. We subtract the space for those we don't * use - which is Not Legal C, but ought to be portable. * OTOH, one can also use a static struct Tile for temporary storage. */ #define assertTileCompatibility(t1,t2) assert((t1)->count==(t2)->count) struct Tile *newTile(struct rect); struct Tile *forkTile(struct Tile*); void freeTile(struct Tile*); #define invalidateSummary(tile,mask) \ do{ assert((tile)->refcount==1); (tile)->summary &= mask; } while(0) summary_t __ATTRIBUTE__((pure)) tileSummary(struct Tile *); void fillTile(struct Tile*,rgba); /* applyMask() destructively changes tile, * applyMask() gets ownership of mask */ void applyMask(struct Tile *tile, struct Tile *mask); struct Tile *getLayerTile(struct xcfLayer *,const struct rect *); struct Tile * getMaskOrLayerTile(struct tileDimensions *dim, struct xcfTiles *tiles, struct rect want); #endif /* FLATTEN_H */ diff --git a/plugins/impex/xcf/3rdparty/xcftools/utils.c b/plugins/impex/xcf/3rdparty/xcftools/utils.c index 37b5aa8236..06958d0be6 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/utils.c +++ b/plugins/impex/xcf/3rdparty/xcftools/utils.c @@ -1,159 +1,168 @@ /* Generic support functions for Xcftools * * This file was written by Henning Makholm * It is hereby in the public domain. * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #include "xcftools.h" #include #include #include #include const char *progname = "$0" ; int verboseFlag = 0 ; -static void __ATTRIBUTE__((noreturn)) +void vFatalGeneric(int status,const char *format,va_list args) { if( format ) { if( *format == '!' ) { vfprintf(stderr,format+1,args); fprintf(stderr,": %s\n",strerror(errno)); } else { vfprintf(stderr,format,args); fputc('\n',stderr); } } - exit(status); + /* don't exit here - Krita can't handle errors otherwise */ + /* exit(status); */ } void FatalGeneric(int status,const char* format,...) { va_list v; va_start(v,format); if( format ) fprintf(stderr,"%s: ",progname); vFatalGeneric(status,format,v); } void FatalUnexpected(const char* format,...) { va_list v; va_start(v,format); fprintf(stderr,"%s: ",progname); vFatalGeneric(127,format,v) ; } void FatalBadXCF(const char* format,...) { va_list v; va_start(v,format); fprintf(stderr,"%s: %s:\n ",progname,_("Corrupted or malformed XCF file")); vFatalGeneric(125,format,v) ; } -void +int xcfCheckspace(uint32_t addr,int spaceafter,const char *format,...) { if( xcf_length < spaceafter || addr > xcf_length - spaceafter ) { va_list v; va_start(v,format); fprintf(stderr,"%s: %s\n ",progname,_("Corrupted or truncated XCF file")); fprintf(stderr,"(0x%" PRIXPTR " bytes): ",(uintptr_t)xcf_length); vFatalGeneric(125,format,v) ; + return XCF_ERROR; } + return XCF_OK; } void FatalUnsupportedXCF(const char* format,...) { va_list v; va_start(v,format); fprintf(stderr,"%s: %s\n ",progname, _("The image contains features not understood by this program:")); vFatalGeneric(123,format,v) ; } void gpl_blurb(void) { fprintf(stderr,PACKAGE_STRING "\n"); fprintf(stderr, _("Type \"%s -h\" to get an option summary.\n"),progname); - exit(1) ; + /* don't exit here - Krita will close otherwise */ + /* exit(1) ; */ } /* ******************************************************* */ void * xcfmalloc(size_t size) { void *ptr = malloc(size); - if( !ptr ) + if( !ptr ) { FatalUnexpected(_("Out of memory")); + return XCF_PTR_EMPTY; + } return ptr ; } void xcffree(void *block) { if( xcf_file && (uint8_t*)block >= xcf_file && (uint8_t*)block < xcf_file + xcf_length ) ; else free(block); } /* ******************************************************* */ FILE * openout(const char *name) { FILE *newfile ; if( strcmp(name,"-") == 0 ) return stdout ; newfile = fopen(name,"wb") ; - if( newfile == NULL ) + if( newfile == NULL ) { FatalUnexpected(_("!Cannot create file %s"),name); + return XCF_PTR_EMPTY; + } return newfile ; } -void +int closeout(FILE *f,const char *name) { if( f == NULL ) - return ; + return XCF_OK; if( fflush(f) == 0 ) { errno = 0 ; if( !ferror(f) ) { if( fclose(f) == 0 ) - return ; + return XCF_OK; } else if( errno == 0 ) { /* Attempt to coax a valid errno out of the standard library, * following an idea by Bruno Haible * http://lists.gnu.org/archive/html/bug-gnulib/2003-09/msg00157.html */ if( fputc('\0', f) != EOF && fflush(f) == 0 ) errno = EIO ; /* Argh, everything succedes. Just call it an I/O error */ } } FatalUnexpected(_("!Error writing file %s"),name); + return XCF_ERROR; } diff --git a/plugins/impex/xcf/3rdparty/xcftools/xcf-general.c b/plugins/impex/xcf/3rdparty/xcftools/xcf-general.c index e5ae5a6049..c6966526ab 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/xcf-general.c +++ b/plugins/impex/xcf/3rdparty/xcftools/xcf-general.c @@ -1,305 +1,388 @@ /* Generic functions for reading XCF files * * This file was written by Henning Makholm * It is hereby in the public domain. * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #include "xcftools.h" #include #include #ifdef HAVE_ICONV # include #elif !defined(ICONV_CONST) # define ICONV_CONST const #endif uint8_t *xcf_file = 0 ; size_t xcf_length ; int use_utf8 = 0 ; -uint32_t -xcfOffset(uint32_t addr,int spaceafter) +int +xcfOffset(uint32_t addr,int spaceafter, uint32_t* apparent) { - uint32_t apparent ; - xcfCheckspace(addr,4,"(xcfOffset)"); - apparent = xcfL(addr); - xcfCheckspace(apparent,spaceafter, + if (!apparent) { + return XCF_ERROR; + } + if(xcfCheckspace(addr,4,"(xcfOffset)") != XCF_OK) { + return XCF_ERROR; + } + *apparent = xcfL(addr); + if (xcfCheckspace(*apparent,spaceafter, "Too large offset (%" PRIX32 ") at position %" PRIX32, - apparent,addr); - return apparent ; + *apparent,addr) != XCF_OK) { + return XCF_ERROR; + } + return XCF_OK; } int -xcfNextprop(uint32_t *master,uint32_t *body) +xcfNextprop(uint32_t *master,uint32_t *body, PropType *typeOut) { + int response; + + if (typeOut == 0) { + return XCF_ERROR; + } + uint32_t ptr, length, total, minlength ; PropType type ; ptr = *master ; - xcfCheckspace(ptr,8,"(property header)"); + if ((response = xcfCheckspace(ptr,8,"(property header)")) != XCF_OK) { + return XCF_ERROR; + } type = xcfL(ptr); length = xcfL(ptr+4); *body = ptr+8 ; switch(type) { case PROP_COLORMAP: { uint32_t ncolors ; - xcfCheckspace(ptr+8,4,"(colormap length)"); + if ((response = xcfCheckspace(ptr+8,4,"(colormap length)")) != XCF_OK) { + return XCF_ERROR; + } ncolors = xcfL(ptr+8) ; - if( ncolors > 256 ) + if( ncolors > 256 ) { FatalBadXCF("Colormap has %" PRIu32 " entries",ncolors); + return XCF_ERROR; + } + /* Surprise! Some older version of the Gimp computed the wrong length * word, and the _reader_ always just reads three bytes per color * and ignores the length tag! Duplicate this so we too can read * the buggy XCF files. */ length = minlength = 4+3*ncolors; break; } case PROP_COMPRESSION: minlength = 1; break; case PROP_OPACITY: minlength = 4; break; case PROP_APPLY_MASK: minlength = 4; break; case PROP_OFFSETS: minlength = 8; break; case PROP_MODE: minlength = 4; break; default: minlength = 0; break; } - if( length < minlength ) + if( length < minlength ) { FatalBadXCF("Short %s property at %" PRIX32 " (%" PRIu32 "<%" PRIu32 ")", showPropType(type),ptr,length,minlength); + return XCF_ERROR; + } *master = ptr+8+length ; total = 8 + length + (type != PROP_END ? 8 : 0) ; - if( total < length ) /* Check overwrap */ + if( total < length ) { /* Check overwrap */ FatalBadXCF("Overlong property at %" PRIX32, ptr); - xcfCheckspace(ptr,total,"Overlong property at %" PRIX32,ptr) ; - return type ; + return XCF_ERROR; + } + if((response = xcfCheckspace(ptr,total,"Overlong property at %" PRIX32,ptr)) != 0) { + return XCF_ERROR; + } + *typeOut = type; + return XCF_OK; } const char* xcfString(uint32_t ptr,uint32_t *after) { uint32_t length ; unsigned i ; ICONV_CONST char *utf8master ; - xcfCheckspace(ptr,4,"(string length)"); + if (xcfCheckspace(ptr,4,"(string length)") != XCF_OK) { + return XCF_PTR_EMPTY; + } length = xcfL(ptr) ; ptr += 4 ; - xcfCheckspace(ptr,length,"(string)"); + if (xcfCheckspace(ptr,length,"(string)") != XCF_OK) { + return XCF_PTR_EMPTY; + } utf8master = (ICONV_CONST char*)(xcf_file+ptr) ; if( after ) *after = ptr + length ; - if( length == 0 || utf8master[length-1] != 0 ) + if( length == 0 || utf8master[length-1] != 0 ) { FatalBadXCF("String at %" PRIX32 " not zero-terminated",ptr-4); + return XCF_PTR_EMPTY; + } length-- ; if( use_utf8 ) return utf8master ; /* We assume that the local character set includes ASCII... * Check if conversion is needed at all */ for( i=0 ; ; i++ ) { if( i == length ) return utf8master ; /* Only ASCII after all */ - if( utf8master[i] == 0 ) + if( utf8master[i] == 0 ) { FatalBadXCF("String at %" PRIX32 " has embedded zeroes",ptr-4); + return XCF_PTR_EMPTY; + } if( (int8_t) utf8master[i] < 0 ) break ; } #ifdef HAVE_ICONV { size_t targetsize = length+1 ; int sloppy_translation = 0 ; iconv_t cd = iconv_open("//TRANSLIT","UTF-8"); if( cd == (iconv_t) -1 ) { cd = iconv_open("","UTF-8"); sloppy_translation = 1 ; } if( cd == (iconv_t) -1 ) iconv_close(cd) ; /* Give up; perhaps iconv doesn't know UTF-8 */ else while(1) { char *buffer = xcfmalloc(targetsize) ; ICONV_CONST char *inbuf = utf8master ; char *outbuf = buffer ; size_t incount = length ; size_t outcount = targetsize ; while(1) { /* Loop for systems without //ICONV support */ size_t result = iconv(cd,&inbuf,&incount,&outbuf,&outcount) ; if( result == (size_t)-1 && errno == EILSEQ && sloppy_translation && outcount > 0 ) { *outbuf++ = '?' ; outcount-- ; while( (int8_t)*inbuf < 0 ) inbuf++, incount-- ; continue ; } if( result != (size_t)-1 ) { if( outcount == 0 ) errno = E2BIG ; else { *outbuf = 0 ; iconv_close(cd) ; return buffer ; } } break ; } - if( errno == EILSEQ || errno == EINVAL ) + if( errno == EILSEQ || errno == EINVAL ) { FatalBadXCF("Bad UTF-8 encoding '%s' at %" PRIXPTR, inbuf,(uintptr_t)((inbuf-utf8master)+ptr)); + return XCF_PTR_EMPTY; + } if( errno == E2BIG ) { targetsize += 1+incount ; xcffree(buffer) ; continue ; } FatalUnexpected("!iconv on layer name at %"PRIX32,ptr); + return XCF_PTR_EMPTY: } } #endif { static int warned = 0 ; if( !warned ) { fprintf(stderr,_("Warning: one or more layer names could not be\n" " translated to the local character set.\n")); warned = 1 ; } } return utf8master ; } /* ****************************************************************** */ void computeDimensions(struct tileDimensions *d) { d->c.r = d->c.l + d->width ; d->c.b = d->c.t + d->height ; d->tilesx = (d->width+TILE_WIDTH-1)/TILE_WIDTH ; d->tilesy = (d->height+TILE_HEIGHT-1)/TILE_HEIGHT ; d->ntiles = d->tilesx * d->tilesy ; } struct xcfImage XCF ; -void +int getBasicXcfInfo(void) { + uint32_t ptr, data, layerfile ; PropType type ; int i, j ; + + int errorStatus; + uint32_t ptrout; + + if (xcfCheckspace(0,14+7*4,"(very short)") != XCF_OK) { + return XCF_ERROR; + } - xcfCheckspace(0,14+7*4,"(very short)"); if( strcmp((char*)xcf_file,"gimp xcf file") == 0 ) XCF.version = 0 ; else if( xcf_file[13] == 0 && sscanf((char*)xcf_file,"gimp xcf v%d",&XCF.version) == 1 ) ; - else + else { FatalBadXCF(_("Not an XCF file at all (magic not recognized)")); + return XCF_ERROR; + } if (XCF.version < 0 || XCF.version > 3) { - return; + return XCF_ERROR; } XCF.compression = COMPRESS_NONE ; XCF.colormapptr = 0 ; ptr = 14 ; XCF.width = xcfL(ptr); ptr += 4 ; XCF.height = xcfL(ptr); ptr += 4 ; XCF.type = xcfL(ptr); ptr += 4 ; - while( (type = xcfNextprop(&ptr,&data)) != PROP_END ) { + while( (errorStatus = xcfNextprop(&ptr,&data, &type)) != XCF_ERROR && type != PROP_END ) { + if (errorStatus != XCF_OK) { + return XCF_ERROR; + } + switch(type) { case PROP_COLORMAP: XCF.colormapptr = data ; break ; case PROP_COMPRESSION: XCF.compression = xcf_file[data] ; break ; default: /* Ignore unknown properties */ break ; } } layerfile = ptr ; - for( XCF.numLayers = 0 ; xcfOffset(ptr,8*4) ; XCF.numLayers++, ptr+=4 ) - ; + XCF.numLayers = 0; + while (1) { + errorStatus = xcfOffset(ptr,8*4, &ptrout); + if (errorStatus != XCF_OK) { + return XCF_ERROR; + } + if (!ptrout) { + break; + } + XCF.numLayers++; + ptr+=4; + } XCF.layers = xcfmalloc(XCF.numLayers * sizeof(struct xcfLayer)) ; for( i = 0 ; i < XCF.numLayers ; i++ ) { struct xcfLayer *L = XCF.layers + i ; ptr = xcfL(layerfile+4*(XCF.numLayers-1-i)) ; L->mode = GIMP_NORMAL_MODE ; L->opacity = 255 ; L->isVisible = 1 ; L->hasMask = 0 ; L->dim.width = xcfL(ptr); ptr+=4 ; L->dim.height = xcfL(ptr); ptr+=4 ; L->type = xcfL(ptr); ptr+=4 ; L->name = xcfString(ptr,&ptr); + if (L->name == XCF_PTR_EMPTY) { + return XCF_ERROR; + } L->propptr = ptr ; L->isGroup = 0; L->pathLength = 0; L->path = NULL; - while( (type = xcfNextprop(&ptr,&data)) != PROP_END ) { + while( (errorStatus = xcfNextprop(&ptr,&data, &type)) != XCF_ERROR && type != PROP_END ) { + if (errorStatus != XCF_OK) { + xcffree(XCF.layers); + XCF.layers = XCF_PTR_EMPTY; + return XCF_ERROR; + } switch(type) { case PROP_OPACITY: L->opacity = xcfL(data); if( L->opacity > 255 ) L->opacity = 255 ; break ; case PROP_VISIBLE: L->isVisible = xcfL(data) != 0 ; break ; case PROP_APPLY_MASK: L->hasMask = xcfL(data) != 0 ; break ; case PROP_OFFSETS: L->dim.c.l = (int32_t)(xcfL(data )) ; L->dim.c.t = (int32_t)(xcfL(data+4)) ; break ; case PROP_MODE: L->mode = xcfL(data); break ; case PROP_GROUP_ITEM: L->isGroup = 1 ; break; case PROP_ITEM_PATH: L->pathLength = (ptr - data - 2) / 4 ; if ( L->pathLength != 0 ) { L->path = xcfmalloc( L->pathLength * sizeof(unsigned) ) ; for ( j = 0; j!=L->pathLength; j++ ) *(L->path + j) = (unsigned)xcfL(data + 4 * j); } break; default: /* Ignore unknown properties */ break ; } } - xcfCheckspace(ptr,8,"(end of layer %s)",L->name); + if ((errorStatus = xcfCheckspace(ptr,8,"(end of layer %s)",L->name)) != XCF_OK) { + xcffree(XCF.layers); + XCF.layers = XCF_PTR_EMPTY; + return XCF_ERROR; + } L->pixels.tileptrs = 0 ; - L->pixels.hierarchy = xcfOffset(ptr ,4*4); + + if (xcfOffset(ptr , 4*4, &(L->pixels.hierarchy)) != XCF_OK) { + xcffree(XCF.layers); + XCF.layers = XCF_PTR_EMPTY; + return XCF_ERROR; + } L->mask.tileptrs = 0 ; - L->mask.hierarchy = xcfOffset(ptr+4,4*4); + if (xcfOffset(ptr+4, 4*4, &(L->mask.hierarchy)) != XCF_OK) { + xcffree(XCF.layers); + XCF.layers = XCF_PTR_EMPTY; + return XCF_ERROR; + } computeDimensions(&L->dim); + } + return XCF_OK; } diff --git a/plugins/impex/xcf/3rdparty/xcftools/xcftools.h b/plugins/impex/xcf/3rdparty/xcftools/xcftools.h index 7b5eb5f00b..501067fbd9 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/xcftools.h +++ b/plugins/impex/xcf/3rdparty/xcftools/xcftools.h @@ -1,201 +1,207 @@ /* Generic functions and macros for reading XCF files * * This file was written by Henning Makholm * It is hereby in the public domain. * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #ifndef XCFTOOLS_H #define XCFTOOLS_H #include "config.h" #include "enums.h" #include #include #if defined(HAVE_GETTEXT) && defined(ENABLE_NLS) #include #define _(s) gettext(s) void nls_init(void); #else #define _(s) (s) #define nls_init() (void)0 #endif #define N_(s) (s) #if HAVE_INTTYPES_H # define __STDC_FORMAT_MACROS # include #else /* These legacy fall-backs will probably work on every system * that does not supply a inttypes.h ... */ typedef unsigned char uint8_t ; typedef unsigned long int uint32_t; typedef signed char int8_t ; typedef signed long int int32_t ; # define PRIX32 "lX" # define PRIu32 "lu" # define PRIXPTR "lX" #endif #if __GNUC__ # define __ATTRIBUTE__ __attribute__ #else # define __ATTRIBUTE__(x) #endif #if HAVE_NETINET_IN_H # include #elif HAVE_ARPA_INET_H # include #elif WORDS_BIGENDIAN # define ntohl(x) (x) #else static inline uint32_t ntohl(uint32_t a) { return (a << 24) + ((a & 0xFF00) << 8) + ((a >> 8) & 0xFF00) + (a >> 24) ; } #endif #ifndef HAVE_STRCASECMP #define strcasecmp strcmp #endif /* Read a single word value from the XCF file */ /* Use + instead of | because that allows LEA instructions */ #define xcfBE(a) ( ((uint32_t)xcf_file[(a) ] << 24) + \ ((uint32_t)xcf_file[(a)+1] << 16) + \ ((uint32_t)xcf_file[(a)+2] << 8 ) + \ ((uint32_t)xcf_file[(a)+3] ) ) #define xcfLE(a) ( ((uint32_t)xcf_file[(a) ] ) + \ ((uint32_t)xcf_file[(a)+1] << 8 ) + \ ((uint32_t)xcf_file[(a)+2] << 16) + \ ((uint32_t)xcf_file[(a)+3] << 24) ) #if defined(CAN_DO_UNALIGNED_WORDS) # define xcfL(a) ntohl(*(uint32_t *)(xcf_file + (a))) #else # define xcfL(a) ((a) & 3 ? xcfBE(a) : ntohl(*(uint32_t *)(xcf_file + (a)))) #endif /* ****************************************************************** */ /* The following are exported from am OS-specific source file; * io-unix.c on unixish systems. */ void read_or_mmap_xcf(const char* filename, const char *unzipper); void free_or_close_xcf(void); /* ****************************************************************** */ /* utils.c */ + +#define XCF_ERROR 1 +#define XCF_OK 0 +#define XCF_PTR_EMPTY 0 + + extern const char *progname ; extern int verboseFlag ; void *xcfmalloc(size_t size); void xcffree(void*); void FatalGeneric(int status,const char* format,...) - __ATTRIBUTE__((format(printf,2,3),noreturn)) ; + __ATTRIBUTE__((format(printf,2,3))) ; void FatalUnexpected(const char* format,...) - __ATTRIBUTE__((format(printf,1,2),noreturn)) ; + __ATTRIBUTE__((format(printf,1,2))) ; void FatalBadXCF(const char* format,...) - __ATTRIBUTE__((format(printf,1,2),noreturn)) ; + __ATTRIBUTE__((format(printf,1,2))) ; void FatalUnsupportedXCF(const char* format,...) - __ATTRIBUTE__((format(printf,1,2),noreturn)) ; + __ATTRIBUTE__((format(printf,1,2))) ; -void gpl_blurb(void) __ATTRIBUTE__((noreturn)); +void gpl_blurb(void); FILE* openout(const char*); -void closeout(FILE *,const char*); +int closeout(FILE *,const char*); struct rect { int t, b, l, r ; }; #define isSubrect(A,B) \ ((A).l >= (B).l && (A).r <= (B).r && (A).t >= (B).t && (A).b <= (B).b) #define disjointRects(A,B) \ ((A).l >= (B).r || (A).r <= (B).l || (A).t >= (B).b || (A).b <= (B).t) /* ****************************************************************** */ /* xcf-general.c */ extern uint8_t *xcf_file ; extern size_t xcf_length ; extern int use_utf8 ; -void xcfCheckspace(uint32_t addr,int spaceafter, const char *format,...) +int xcfCheckspace(uint32_t addr,int spaceafter, const char *format,...) __ATTRIBUTE__((format(printf,3,4))); -uint32_t xcfOffset(uint32_t addr,int spaceafter); +int xcfOffset(uint32_t addr,int spaceafter, uint32_t* apparent); -int xcfNextprop(uint32_t *master,uint32_t *body); +int xcfNextprop(uint32_t *master,uint32_t *body, PropType* type); const char* xcfString(uint32_t ptr,uint32_t *after); /* These are hardcoded in the Gimp sources: */ #define TILE_SHIFT 6 #define TILE_WIDTH (1<> TILE_SHIFT) struct tileDimensions { struct rect c ; unsigned width, height ; unsigned tilesx, tilesy ; unsigned ntiles ; }; /* computeDimensions assumes that width, height, c.l, and c.t are set */ void computeDimensions(struct tileDimensions *); struct xcfTiles { const struct _convertParams *params ; uint32_t *tileptrs ; uint32_t hierarchy ; }; struct xcfLayer { struct tileDimensions dim ; const char *name ; GimpLayerModeEffects mode ; GimpImageType type ; unsigned int opacity ; int isVisible, hasMask ; uint32_t propptr ; struct xcfTiles pixels ; struct xcfTiles mask ; int isGroup ; unsigned pathLength ; unsigned *path ; }; extern struct xcfImage { int version ; unsigned width, height ; GimpImageBaseType type ; XcfCompressionType compression ; int numLayers ; struct xcfLayer *layers ; uint32_t colormapptr ; } XCF ; -void getBasicXcfInfo(void); +int getBasicXcfInfo(void); #endif /* XCFTOOLS_H */ diff --git a/plugins/impex/xcf/kis_xcf_import.cpp b/plugins/impex/xcf/kis_xcf_import.cpp index 138c82e4e9..41c14801f8 100644 --- a/plugins/impex/xcf/kis_xcf_import.cpp +++ b/plugins/impex/xcf/kis_xcf_import.cpp @@ -1,317 +1,330 @@ /* * Copyright (c) 2009 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_xcf_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include "kis_types.h" #include extern "C" { #include "xcftools.h" #include "pixels.h" #define GET_RED(x) (x >> RED_SHIFT) #define GET_GREEN(x) (x >> GREEN_SHIFT) #define GET_BLUE(x) (x >> BLUE_SHIFT) #define GET_ALPHA(x) (x >> ALPHA_SHIFT) } QString layerModeG2K(GimpLayerModeEffects mode) { switch (mode) { case GIMP_NORMAL_MODE: return COMPOSITE_OVER; case GIMP_DISSOLVE_MODE: return COMPOSITE_DISSOLVE; case GIMP_MULTIPLY_MODE: return COMPOSITE_MULT; case GIMP_SCREEN_MODE: return COMPOSITE_SCREEN; case GIMP_OVERLAY_MODE: case GIMP_SOFTLIGHT_MODE: return COMPOSITE_OVERLAY; case GIMP_DIFFERENCE_MODE: return COMPOSITE_DIFF; case GIMP_ADDITION_MODE: return COMPOSITE_ADD; case GIMP_SUBTRACT_MODE: return COMPOSITE_SUBTRACT; case GIMP_DARKEN_ONLY_MODE: return COMPOSITE_DARKEN; case GIMP_LIGHTEN_ONLY_MODE: return COMPOSITE_LIGHTEN; case GIMP_HUE_MODE: return COMPOSITE_HUE_HSL; case GIMP_SATURATION_MODE: return COMPOSITE_SATURATION_HSV; case GIMP_COLOR_MODE: return COMPOSITE_COLOR_HSL; case GIMP_VALUE_MODE: return COMPOSITE_VALUE; case GIMP_DIVIDE_MODE: return COMPOSITE_DIVIDE; case GIMP_DODGE_MODE: return COMPOSITE_DODGE; case GIMP_BURN_MODE: return COMPOSITE_BURN; case GIMP_ERASE_MODE: return COMPOSITE_ERASE; case GIMP_REPLACE_MODE: return COMPOSITE_COPY; case GIMP_HARDLIGHT_MODE: return COMPOSITE_HARD_LIGHT; case GIMP_COLOR_ERASE_MODE: case GIMP_NORMAL_NOPARTIAL_MODE: case GIMP_ANTI_ERASE_MODE: case GIMP_GRAIN_EXTRACT_MODE: return COMPOSITE_GRAIN_EXTRACT; case GIMP_GRAIN_MERGE_MODE: return COMPOSITE_GRAIN_MERGE; case GIMP_BEHIND_MODE: break; } dbgFile << "Unknown mode: " << mode; return COMPOSITE_OVER; } struct Layer { KisLayerSP layer; int depth; KisMaskSP mask; }; KisGroupLayerSP findGroup(const QVector &layers, const Layer& layer, int i) { for (; i < layers.size(); ++i) { KisGroupLayerSP group = dynamic_cast(const_cast(layers[i].layer.data())); if (group && (layers[i].depth == layer.depth -1)) { return group; } } return 0; } void addLayers(const QVector &layers, KisImageSP image, int depth) { for(int i = 0; i < layers.size(); i++) { const Layer &layer = layers[i]; if (layer.depth == depth) { KisGroupLayerSP group = (depth == 0 ? image->rootLayer() : findGroup(layers, layer, i)); image->addNode(layer.layer, group); if (layer.mask) { image->addNode(layer.mask, layer.layer); } } } } K_PLUGIN_FACTORY_WITH_JSON(XCFImportFactory, "krita_xcf_import.json", registerPlugin();) KisXCFImport::KisXCFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisXCFImport::~KisXCFImport() { } -KisImportExportFilter::ConversionStatus KisXCFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisXCFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { + int errorStatus; + dbgFile << "Start decoding file"; QByteArray data = io->readAll(); xcf_file = (uint8_t*)data.data(); xcf_length = data.size(); io->close(); // Decode the data - getBasicXcfInfo() ; - - if (XCF.version < 0 || XCF.version > 3) { - document->setErrorMessage(i18n("This XCF file is too new; Krita cannot support XCF files written by GIMP 2.9 or newer.")); - return KisImportExportFilter::UnsupportedVersion; + if (getBasicXcfInfo() != XCF_OK) { + if (XCF.version < 0 || XCF.version > 3) { + document->setErrorMessage(i18n("This XCF file is too new; Krita cannot support XCF files written by GIMP 2.9 or newer.")); + return ImportExportCodes::FormatFeaturesUnsupported; + } + return ImportExportCodes::FileFormatIncorrect; } - initColormap(); + if(initColormap() != XCF_OK) { + return ImportExportCodes::FileFormatIncorrect; + } dbgFile << XCF.version << "width = " << XCF.width << "height = " << XCF.height << "layers = " << XCF.numLayers; // Create the image KisImageSP image = new KisImage(document->createUndoStore(), XCF.width, XCF.height, KoColorSpaceRegistry::instance()->rgb8(), "built image"); QVector layers; uint maxDepth = 0; // Read layers for (int i = 0; i < XCF.numLayers; ++i) { Layer layer; xcfLayer& xcflayer = XCF.layers[i]; dbgFile << i << " name = " << xcflayer.name << " opacity = " << xcflayer.opacity << "group:" << xcflayer.isGroup << xcflayer.pathLength; dbgFile << ppVar(xcflayer.dim.width) << ppVar(xcflayer.dim.height) << ppVar(xcflayer.dim.tilesx) << ppVar(xcflayer.dim.tilesy) << ppVar(xcflayer.dim.ntiles) << ppVar(xcflayer.dim.c.t) << ppVar(xcflayer.dim.c.l) << ppVar(xcflayer.dim.c.r) << ppVar(xcflayer.dim.c.b); maxDepth = qMax(maxDepth, xcflayer.pathLength); bool isRgbA = false; // Select the color space const KoColorSpace* colorSpace = 0; switch (xcflayer.type) { case GIMP_INDEXED_IMAGE: case GIMP_INDEXEDA_IMAGE: case GIMP_RGB_IMAGE: case GIMP_RGBA_IMAGE: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); isRgbA = true; break; case GIMP_GRAY_IMAGE: case GIMP_GRAYA_IMAGE: colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), ""); isRgbA = false; break; } // Create the layer KisLayerSP kisLayer; if (xcflayer.isGroup) { kisLayer = new KisGroupLayer(image, QString::fromUtf8(xcflayer.name), xcflayer.opacity); } else { kisLayer = new KisPaintLayer(image, QString::fromUtf8(xcflayer.name), xcflayer.opacity, colorSpace); } // Set some properties kisLayer->setCompositeOpId(layerModeG2K(xcflayer.mode)); kisLayer->setVisible(xcflayer.isVisible); kisLayer->disableAlphaChannel(xcflayer.mode != GIMP_NORMAL_MODE); layer.layer = kisLayer; layer.depth = xcflayer.pathLength; // Copy the data in the image - initLayer(&xcflayer); + if ((errorStatus = initLayer(&xcflayer)) != XCF_OK) { + return ImportExportCodes::FileFormatIncorrect; + } int left = xcflayer.dim.c.l; int top = xcflayer.dim.c.t; if (!xcflayer.isGroup) { // Copy the data; for (unsigned int x = 0; x < xcflayer.dim.width; x += TILE_WIDTH) { for (unsigned int y = 0; y < xcflayer.dim.height; y += TILE_HEIGHT) { rect want; want.l = x + left; want.t = y + top; want.b = want.t + TILE_HEIGHT; want.r = want.l + TILE_WIDTH; Tile* tile = getMaskOrLayerTile(&xcflayer.dim, &xcflayer.pixels, want); + if (tile == XCF_PTR_EMPTY) { + return ImportExportCodes::FileFormatIncorrect; + } KisHLineIteratorSP it = kisLayer->paintDevice()->createHLineIteratorNG(x, y, TILE_WIDTH); rgba* data = tile->pixels; for (int v = 0; v < TILE_HEIGHT; ++v) { if (isRgbA) { // RGB image do { KoBgrTraits::setRed(it->rawData(), GET_RED(*data)); KoBgrTraits::setGreen(it->rawData(), GET_GREEN(*data)); KoBgrTraits::setBlue(it->rawData(), GET_BLUE(*data)); KoBgrTraits::setOpacity(it->rawData(), quint8(GET_ALPHA(*data)), 1); ++data; } while (it->nextPixel()); } else { // Grayscale image do { it->rawData()[0] = GET_RED(*data); it->rawData()[1] = GET_ALPHA(*data); ++data; } while (it->nextPixel()); } it->nextRow(); } } } // Move the layer to its position kisLayer->paintDevice()->setX(left); kisLayer->paintDevice()->setY(top); } // Create the mask if (xcflayer.hasMask) { KisTransparencyMaskSP mask = new KisTransparencyMask(); layer.mask = mask; mask->initSelection(kisLayer); for (unsigned int x = 0; x < xcflayer.dim.width; x += TILE_WIDTH) { for (unsigned int y = 0; y < xcflayer.dim.height; y += TILE_HEIGHT) { rect want; want.l = x + left; want.t = y + top; want.b = want.t + TILE_HEIGHT; want.r = want.l + TILE_WIDTH; Tile* tile = getMaskOrLayerTile(&xcflayer.dim, &xcflayer.mask, want); + if (tile == XCF_PTR_EMPTY) { + return ImportExportCodes::FileFormatIncorrect; + } KisHLineIteratorSP it = mask->paintDevice()->createHLineIteratorNG(x, y, TILE_WIDTH); rgba* data = tile->pixels; for (int v = 0; v < TILE_HEIGHT; ++v) { do { it->rawData()[0] = GET_ALPHA(*data); ++data; } while (it->nextPixel()); it->nextRow(); } } } mask->paintDevice()->setX(left); mask->paintDevice()->setY(top); } dbgFile << xcflayer.pixels.tileptrs; layers.append(layer); } for (uint i = 0; i <= maxDepth; ++i) { addLayers(layers, image, i); } document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_xcf_import.moc" diff --git a/plugins/impex/xcf/kis_xcf_import.h b/plugins/impex/xcf/kis_xcf_import.h index cfbe32ebf6..67683cf51b 100644 --- a/plugins/impex/xcf/kis_xcf_import.h +++ b/plugins/impex/xcf/kis_xcf_import.h @@ -1,40 +1,40 @@ /* * Copyright (c) 2009 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_XCF_IMPORT_H_ #define _KIS_XCF_IMPORT_H_ #include #include #include class KisDocument; class KisXCFImport : public KisImportExportFilter { Q_OBJECT public: KisXCFImport(QObject *parent, const QVariantList &); ~KisXCFImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/xcf/tests/data/incorrectFormatFile.txt b/plugins/impex/xcf/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/xcf/tests/data/readonlyFile.txt b/plugins/impex/xcf/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/xcf/tests/data/writeonlyFile.txt b/plugins/impex/xcf/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/xcf/tests/kis_xcf_test.cpp b/plugins/impex/xcf/tests/kis_xcf_test.cpp index 25c1c90c2a..51546a467a 100644 --- a/plugins/impex/xcf/tests/kis_xcf_test.cpp +++ b/plugins/impex/xcf/tests/kis_xcf_test.cpp @@ -1,38 +1,62 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_xcf_test.h" #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString XcfMimetype = "image/x-xcf"; + + void KisXCFTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); } + + +void KisXCFTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), XcfMimetype); +} + +/* +void KisXCFTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), XcfMimetype); +} +*/ + + +void KisXCFTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), XcfMimetype); +} + + KISTEST_MAIN(KisXCFTest) diff --git a/plugins/impex/xcf/tests/kis_xcf_test.h b/plugins/impex/xcf/tests/kis_xcf_test.h index 6ca18ec23d..11e3f37c72 100644 --- a/plugins/impex/xcf/tests/kis_xcf_test.h +++ b/plugins/impex/xcf/tests/kis_xcf_test.h @@ -1,31 +1,36 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_XCF_TEST_H_ #define _KIS_XCF_TEST_H_ #include class KisXCFTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + + void testImportFromWriteonly(); + // You can't export to xcf + /* void testExportToReadonly(); */ + void testImportIncorrectFormat(); }; #endif diff --git a/plugins/paintops/hairy/hairy_brush.cpp b/plugins/paintops/hairy/hairy_brush.cpp index 5a59152cb5..4a71505402 100644 --- a/plugins/paintops/hairy/hairy_brush.cpp +++ b/plugins/paintops/hairy/hairy_brush.cpp @@ -1,444 +1,449 @@ /* * Copyright (c) 2008-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "hairy_brush.h" #include #include #include #include #include #include #include #include #include #include #include #include #include HairyBrush::HairyBrush() { m_counter = 0; m_lastAngle = 0.0; m_oldPressure = 1.0f; m_saturationId = -1; m_transfo = 0; } HairyBrush::~HairyBrush() { delete m_transfo; qDeleteAll(m_bristles.begin(), m_bristles.end()); m_bristles.clear(); } void HairyBrush::initAndCache() { m_compositeOp = m_dab->colorSpace()->compositeOp(COMPOSITE_OVER); m_pixelSize = m_dab->colorSpace()->pixelSize(); if (m_properties->useSaturation) { m_transfo = m_dab->colorSpace()->createColorTransformation("hsv_adjustment", m_params); if (m_transfo) { m_saturationId = m_transfo->parameterId("s"); } } } void HairyBrush::fromDabWithDensity(KisFixedPaintDeviceSP dab, qreal density) { int width = dab->bounds().width(); int height = dab->bounds().height(); int centerX = width * 0.5; int centerY = height * 0.5; // make mask Bristle * bristle = 0; qreal alpha; quint8 * dabPointer = dab->data(); quint8 pixelSize = dab->pixelSize(); const KoColorSpace * cs = dab->colorSpace(); KoColor bristleColor(cs); KisRandomSource randomSource(0); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { alpha = cs->opacityF(dabPointer); if (alpha != 0.0) { if (density == 1.0 || randomSource.generateNormalized() <= density) { memcpy(bristleColor.data(), dabPointer, pixelSize); bristle = new Bristle(x - centerX, y - centerY, alpha); // using value from image as length of bristle bristle->setColor(bristleColor); m_bristles.append(bristle); } } dabPointer += pixelSize; } } } void HairyBrush::paintLine(KisPaintDeviceSP dab, KisPaintDeviceSP layer, const KisPaintInformation &pi1, const KisPaintInformation &pi2, qreal scale, qreal rotation) { m_counter++; qreal x1 = pi1.pos().x(); qreal y1 = pi1.pos().y(); qreal x2 = pi2.pos().x(); qreal y2 = pi2.pos().y(); qreal dx = x2 - x1; qreal dy = y2 - y1; // TODO:this angle is different from the drawing angle in sensor (info.angle()). The bug is caused probably due to // not computing the drag vector properly in paintBezierLine when smoothing is used //qreal angle = atan2(dy, dx); qreal angle = rotation; qreal mousePressure = 1.0; if (m_properties->useMousePressure) { // want pressure from mouse movement qreal distance = sqrt(dx * dx + dy * dy); mousePressure = (1.0 - computeMousePressure(distance)); scale *= mousePressure; } // this pressure controls shear and ink depletion qreal pressure = mousePressure * (pi2.pressure() * 2); Bristle *bristle = 0; KoColor bristleColor(dab->colorSpace()); m_dabAccessor = dab->createRandomAccessorNG((int)x1, (int)y1); m_dab = dab; // initialization block if (firstStroke()) { initAndCache(); } /*If this is first time the brush touches the canvas and we are using soak ink while ink depletion is enabled...*/ if (m_properties->inkDepletionEnabled && firstStroke() && m_properties->useSoakInk) { if (layer) { colorifyBristles(layer, pi1.pos()); } else { dbgKrita << "Can't soak the ink from the layer"; } } KisRandomSourceSP randomSource = pi2.randomSource(); qreal fx1, fy1, fx2, fy2; qreal randomX, randomY; qreal shear; float inkDeplation = 0.0; int inkDepletionSize = m_properties->inkDepletionCurve.size(); int bristleCount = m_bristles.size(); int bristlePathSize; qreal threshold = 1.0 - pi2.pressure(); for (int i = 0; i < bristleCount; i++) { if (!m_bristles.at(i)->enabled()) continue; bristle = m_bristles[i]; randomX = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor; randomY = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor; shear = pressure * m_properties->shearFactor; m_transform.reset(); m_transform.rotateRadians(-angle); m_transform.scale(scale, scale); m_transform.translate(randomX, randomY); m_transform.shear(shear, shear); if (firstStroke() || (!m_properties->connectedPath)) { // transform start dab m_transform.map(bristle->x(), bristle->y(), &fx1, &fy1); // transform end dab m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2); } else { // continue the path of the bristle from the previous position fx1 = bristle->prevX(); fy1 = bristle->prevY(); m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2); } // remember the end point bristle->setPrevX(fx2); bristle->setPrevY(fy2); // all coords relative to device position fx1 += x1; fy1 += y1; fx2 += x2; fy2 += y2; if (m_properties->threshold && (bristle->length() < threshold)) continue; // paint between first and last dab const QVector bristlePath = m_trajectory.getLinearTrajectory(QPointF(fx1, fy1), QPointF(fx2, fy2), 1.0); bristlePathSize = m_trajectory.size(); + // avoid overlapping bristle caps with antialias on + if (m_properties->antialias) { + bristlePathSize -= 1; + } + memcpy(bristleColor.data(), bristle->color().data() , m_pixelSize); for (int i = 0; i < bristlePathSize ; i++) { if (m_properties->inkDepletionEnabled) { inkDeplation = fetchInkDepletion(bristle, inkDepletionSize); if (m_properties->useSaturation && m_transfo != 0) { saturationDepletion(bristle, bristleColor, pressure, inkDeplation); } if (m_properties->useOpacity) { opacityDepletion(bristle, bristleColor, pressure, inkDeplation); } } else { if (bristleColor.opacityU8() != 0) { bristleColor.setOpacity(bristle->length()); } } addBristleInk(bristle, bristlePath.at(i), bristleColor); bristle->setInkAmount(1.0 - inkDeplation); bristle->upIncrement(); } } m_dab = 0; m_dabAccessor = 0; } inline qreal HairyBrush::fetchInkDepletion(Bristle* bristle, int inkDepletionSize) { if (bristle->counter() >= inkDepletionSize - 1) { return m_properties->inkDepletionCurve[inkDepletionSize - 1]; } else { return m_properties->inkDepletionCurve[bristle->counter()]; } } void HairyBrush::saturationDepletion(Bristle * bristle, KoColor &bristleColor, qreal pressure, qreal inkDeplation) { qreal saturation; if (m_properties->useWeights) { // new weighted way (experiment) saturation = ( (pressure * m_properties->pressureWeight) + (bristle->length() * m_properties->bristleLengthWeight) + (bristle->inkAmount() * m_properties->bristleInkAmountWeight) + ((1.0 - inkDeplation) * m_properties->inkDepletionWeight)) - 1.0; } else { // old way of computing saturation saturation = ( pressure * bristle->length() * bristle->inkAmount() * (1.0 - inkDeplation)) - 1.0; } m_transfo->setParameter(m_transfo->parameterId("h"), 0.0); m_transfo->setParameter(m_transfo->parameterId("v"), 0.0); m_transfo->setParameter(m_saturationId, saturation); m_transfo->setParameter(3, 1);//sets the type to m_transfo->setParameter(4, false);//sets the colorize to none. m_transfo->transform(bristleColor.data(), bristleColor.data() , 1); } void HairyBrush::opacityDepletion(Bristle* bristle, KoColor& bristleColor, qreal pressure, qreal inkDeplation) { qreal opacity = OPACITY_OPAQUE_F; if (m_properties->useWeights) { opacity = pressure * m_properties->pressureWeight + bristle->length() * m_properties->bristleLengthWeight + bristle->inkAmount() * m_properties->bristleInkAmountWeight + (1.0 - inkDeplation) * m_properties->inkDepletionWeight; } else { opacity = bristle->length() * bristle->inkAmount(); } opacity = qBound(0.0, opacity, 1.0); bristleColor.setOpacity(opacity); } inline void HairyBrush::addBristleInk(Bristle *bristle,const QPointF &pos, const KoColor &color) { Q_UNUSED(bristle); if (m_properties->antialias) { if (m_properties->useCompositing) { paintParticle(pos, color); } else { paintParticle(pos, color, 1.0); } } else { int ix = qRound(pos.x()); int iy = qRound(pos.y()); if (m_properties->useCompositing) { plotPixel(ix, iy, color); } else { darkenPixel(ix, iy, color); } } } void HairyBrush::paintParticle(QPointF pos, const KoColor& color, qreal weight) { // opacity top left, right, bottom left, right quint8 opacity = color.opacityU8(); opacity *= weight; int ipx = int (pos.x()); int ipy = int (pos.y()); - qreal fx = pos.x() - ipx; - qreal fy = pos.y() - ipy; + qreal fx = qAbs(pos.x() - ipx); + qreal fy = qAbs(pos.y() - ipy); quint8 btl = qRound((1.0 - fx) * (1.0 - fy) * opacity); quint8 btr = qRound((fx) * (1.0 - fy) * opacity); quint8 bbl = qRound((1.0 - fx) * (fy) * opacity); quint8 bbr = qRound((fx) * (fy) * opacity); const KoColorSpace * cs = m_dab->colorSpace(); m_dabAccessor->moveTo(ipx , ipy); btl = quint8(qBound(OPACITY_TRANSPARENT_U8, btl + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8)); memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize()); cs->setOpacity(m_dabAccessor->rawData(), btl, 1); m_dabAccessor->moveTo(ipx + 1, ipy); btr = quint8(qBound(OPACITY_TRANSPARENT_U8, btr + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8)); memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize()); cs->setOpacity(m_dabAccessor->rawData(), btr, 1); m_dabAccessor->moveTo(ipx, ipy + 1); bbl = quint8(qBound(OPACITY_TRANSPARENT_U8, bbl + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8)); memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize()); cs->setOpacity(m_dabAccessor->rawData(), bbl, 1); m_dabAccessor->moveTo(ipx + 1, ipy + 1); bbr = quint8(qBound(OPACITY_TRANSPARENT_U8, bbr + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8)); memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize()); cs->setOpacity(m_dabAccessor->rawData(), bbr, 1); } void HairyBrush::paintParticle(QPointF pos, const KoColor& color) { // opacity top left, right, bottom left, right memcpy(m_color.data(), color.data(), m_pixelSize); quint8 opacity = color.opacityU8(); int ipx = int (pos.x()); int ipy = int (pos.y()); - qreal fx = pos.x() - ipx; - qreal fy = pos.y() - ipy; + qreal fx = qAbs(pos.x() - ipx); + qreal fy = qAbs(pos.y() - ipy); quint8 btl = qRound((1.0 - fx) * (1.0 - fy) * opacity); quint8 btr = qRound((fx) * (1.0 - fy) * opacity); quint8 bbl = qRound((1.0 - fx) * (fy) * opacity); quint8 bbr = qRound((fx) * (fy) * opacity); m_color.setOpacity(btl); plotPixel(ipx , ipy, m_color); m_color.setOpacity(btr); plotPixel(ipx + 1 , ipy, m_color); m_color.setOpacity(bbl); plotPixel(ipx , ipy + 1, m_color); m_color.setOpacity(bbr); plotPixel(ipx + 1 , ipy + 1, m_color); } inline void HairyBrush::plotPixel(int wx, int wy, const KoColor &color) { m_dabAccessor->moveTo(wx, wy); m_compositeOp->composite(m_dabAccessor->rawData(), m_pixelSize, color.data() , m_pixelSize, 0, 0, 1, 1, OPACITY_OPAQUE_U8); } inline void HairyBrush::darkenPixel(int wx, int wy, const KoColor &color) { m_dabAccessor->moveTo(wx, wy); if (m_dab->colorSpace()->opacityU8(m_dabAccessor->rawData()) < color.opacityU8()) { memcpy(m_dabAccessor->rawData(), color.data(), m_pixelSize); } } double HairyBrush::computeMousePressure(double distance) { static const double scale = 20.0; static const double minPressure = 0.02; double oldPressure = m_oldPressure; double factor = 1.0 - distance / scale; if (factor < 0.0) factor = 0.0; double result = ((4.0 * oldPressure) + minPressure + factor) / 5.0; m_oldPressure = result; return result; } void HairyBrush::colorifyBristles(KisPaintDeviceSP source, QPointF point) { KoColor bristleColor(m_dab->colorSpace()); KisCrossDeviceColorPickerInt colorPicker(source, bristleColor); Bristle *b = 0; int size = m_bristles.size(); for (int i = 0; i < size; i++) { b = m_bristles[i]; int x = qRound(b->x() + point.x()); int y = qRound(b->y() + point.y()); colorPicker.pickOldColor(x, y, bristleColor.data()); b->setColor(bristleColor); } } diff --git a/plugins/paintops/hairy/trajectory.cpp b/plugins/paintops/hairy/trajectory.cpp index dc0518d7fa..2b83fac4b0 100644 --- a/plugins/paintops/hairy/trajectory.cpp +++ b/plugins/paintops/hairy/trajectory.cpp @@ -1,166 +1,167 @@ /* * Copyright (c) 2008-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "trajectory.h" #include #include Trajectory::Trajectory() { m_i = 0; m_size = 0; } Trajectory::~Trajectory() { } void Trajectory::addPoint(QPointF pos) { if (m_i >= m_path.size()) { m_path.append(pos); m_i++; } else { m_path[m_i] = pos; m_i++; } m_size++; } void Trajectory::reset() { m_size = 0; m_i = 0; } const QVector &Trajectory::getLinearTrajectory(const QPointF &start, const QPointF &end, double space) { Q_UNUSED(space); reset(); // Width and height of the line - float xd = (end.x() - start.x()); - float yd = (end.y() - start.y()); + qreal xd = (end.x() - start.x()); + qreal yd = (end.y() - start.y()); int x = (int)start.x(); int y = (int)start.y(); - float fx = start.x(); - float fy = start.y(); - float m = yd / xd; + qreal fx = start.x(); + qreal fy = start.y(); + qreal m = yd / xd; int y2 = (int)end.y(); int x2 = (int)end.x(); - //m_path.append(start); addPoint(start); if (fabs(m) > 1) { // y - directional axis int incr; if (yd > 0) { m = 1.0f / m; incr = 1; } else { m = -1.0f / m; incr = -1; } while (y != y2) { fx = fx + m; - y = y + incr; - x = (int)(fx + 0.5f); - addPoint(QPointF(fx, y)); + fy = fy + incr; + y += incr; +// x = (int)(fx + 0.5f); + addPoint(QPointF(fx, fy)); } } else { // x - directional axis int incr; if (xd > 0) { incr = 1; } else { incr = -1; m = -m; } while (x != x2) { fy = fy + m; - x = x + incr; - y = (int)(fy + 0.5f); - addPoint(QPointF(x, fy)); + fx = fx + incr; + x += incr; +// y = (int)(fy + 0.5f); + addPoint(QPointF(fx, fy)); } } addPoint(end); return m_path; } QVector Trajectory::getDDATrajectory(QPointF start, QPointF end, double space) { Q_UNUSED(space); reset(); // Width and height of the line int xd = (int)(end.x() - start.x()); int yd = (int)(end.y() - start.y()); int x = (int)start.x(); int y = (int)start.y(); float fx = start.x(); float fy = start.y(); float m = (float)yd / (float)xd; int y2 = (int)end.y(); int x2 = (int)end.x(); if (fabs(m) > 1) { int incr; if (yd > 0) { m = 1.0f / m; incr = 1; } else { m = -1.0f / m; incr = -1; } while (y != y2) { fx = fx + m; y = y + incr; x = (int)(fx + 0.5f); addPoint(QPointF(x, y)); } } else { int incr; if (xd > 0) { incr = 1; } else { incr = -1; m = -m; } while (x != x2) { fy = fy + m; x = x + incr; y = (int)(fy + 0.5f); addPoint(QPointF(x, y)); } } return m_path; } diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp index af512b2a32..f9040ecb54 100644 --- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp @@ -1,520 +1,524 @@ /* This file is part of the KDE project * Copyright (C) 2008 Fela Winkelmolen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KarbonCalligraphyTool.h" #include "KarbonCalligraphicShape.h" #include "KarbonCalligraphyOptionWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef M_PI const qreal M_PI = 3.1415927; using std::pow; using std::sqrt; KarbonCalligraphyTool::KarbonCalligraphyTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_shape(0) , m_angle(0) , m_selectedPath(0) , m_isDrawing(false) , m_speed(0, 0) , m_lastShape(0) { connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), SLOT(updateSelectedPath())); updateSelectedPath(); } KarbonCalligraphyTool::~KarbonCalligraphyTool() { } void KarbonCalligraphyTool::paint(QPainter &painter, const KoViewConverter &converter) { if (m_selectedPath) { painter.save(); painter.setRenderHints(QPainter::Antialiasing, false); painter.setPen(Qt::red); // TODO make configurable QRectF rect = m_selectedPath->boundingRect(); QPointF p1 = converter.documentToView(rect.topLeft()); QPointF p2 = converter.documentToView(rect.bottomRight()); painter.drawRect(QRectF(p1, p2)); painter.restore(); } if (!m_shape) { return; } painter.save(); painter.setTransform(m_shape->absoluteTransformation(&converter) * painter.transform()); KoShapePaintingContext paintContext; //FIXME m_shape->paint(painter, converter, paintContext); painter.restore(); } void KarbonCalligraphyTool::mousePressEvent(KoPointerEvent *event) { if (m_isDrawing) { return; } m_lastPoint = event->point; m_speed = QPointF(0, 0); m_isDrawing = true; m_pointCount = 0; m_shape = new KarbonCalligraphicShape(m_caps); m_shape->setBackground(QSharedPointer(new KoColorBackground(canvas()->resourceManager()->foregroundColor().toQColor()))); //addPoint( event ); } void KarbonCalligraphyTool::mouseMoveEvent(KoPointerEvent *event) { if (!m_isDrawing) { return; } addPoint(event); } void KarbonCalligraphyTool::mouseReleaseEvent(KoPointerEvent *event) { if (!m_isDrawing) { return; } if (m_pointCount == 0) { // handle click: select shape (if any) if (event->point == m_lastPoint) { KoShapeManager *shapeManager = canvas()->shapeManager(); KoShape *selectedShape = shapeManager->shapeAt(event->point); if (selectedShape != 0) { shapeManager->selection()->deselectAll(); shapeManager->selection()->select(selectedShape); } } delete m_shape; m_shape = 0; m_isDrawing = false; return; } else { m_endOfPath = false; // allow last point being added addPoint(event); // add last point m_isDrawing = false; } m_shape->simplifyGuidePath(); KUndo2Command *cmd = canvas()->shapeController()->addShape(m_shape, 0); if (cmd) { m_lastShape = m_shape; canvas()->addCommand(cmd); canvas()->updateCanvas(m_shape->boundingRect()); } else { // don't leak shape when command could not be created delete m_shape; } m_shape = 0; } void KarbonCalligraphyTool::addPoint(KoPointerEvent *event) { if (m_pointCount == 0) { if (m_usePath && m_selectedPath) { m_selectedPathOutline = m_selectedPath->outline(); } m_pointCount = 1; m_endOfPath = false; m_followPathPosition = 0; m_lastMousePos = event->point; m_lastPoint = calculateNewPoint(event->point, &m_speed); m_deviceSupportsTilt = (event->xTilt() != 0 || event->yTilt() != 0); return; } if (m_endOfPath) { return; } ++m_pointCount; setAngle(event); QPointF newSpeed; QPointF newPoint = calculateNewPoint(event->point, &newSpeed); qreal width = calculateWidth(event->pressure()); qreal angle = calculateAngle(m_speed, newSpeed); // add the previous point m_shape->appendPoint(m_lastPoint, angle, width); m_speed = newSpeed; m_lastPoint = newPoint; canvas()->updateCanvas(m_shape->lastPieceBoundingRect()); if (m_usePath && m_selectedPath) { m_speed = QPointF(0, 0); // following path } } void KarbonCalligraphyTool::setAngle(KoPointerEvent *event) { if (!m_useAngle) { m_angle = (360 - m_customAngle + 90) / 180.0 * M_PI; return; } // setting m_angle to the angle of the device if (event->xTilt() != 0 || event->yTilt() != 0) { m_deviceSupportsTilt = false; } if (m_deviceSupportsTilt) { if (event->xTilt() == 0 && event->yTilt() == 0) { return; // leave as is } qDebug() << "using tilt" << m_angle; if (event->x() == 0) { m_angle = M_PI / 2; return; } // y is inverted in qt painting m_angle = std::atan(static_cast(-event->yTilt() / event->xTilt())) + M_PI / 2; } else { m_angle = event->rotation() + M_PI / 2; qDebug() << "using rotation" << m_angle; } } QPointF KarbonCalligraphyTool::calculateNewPoint(const QPointF &mousePos, QPointF *speed) { if (!m_usePath || !m_selectedPath) { // don't follow path QPointF force = mousePos - m_lastPoint; QPointF dSpeed = force / m_mass; *speed = m_speed * (1.0 - m_drag) + dSpeed; return m_lastPoint + *speed; } QPointF sp = mousePos - m_lastMousePos; m_lastMousePos = mousePos; // follow selected path qreal step = QLineF(QPointF(0, 0), sp).length(); m_followPathPosition += step; qreal t; if (m_followPathPosition >= m_selectedPathOutline.length()) { t = 1.0; m_endOfPath = true; } else { t = m_selectedPathOutline.percentAtLength(m_followPathPosition); } QPointF res = m_selectedPathOutline.pointAtPercent(t) + m_selectedPath->position(); *speed = res - m_lastPoint; return res; } qreal KarbonCalligraphyTool::calculateWidth(qreal pressure) { // calculate the modulo of the speed qreal speed = std::sqrt(pow(m_speed.x(), 2) + pow(m_speed.y(), 2)); qreal thinning = m_thinning * (speed + 1) / 10.0; // can be negative if (thinning > 1) { thinning = 1; } if (!m_usePressure) { pressure = 1.0; } qreal strokeWidth = m_strokeWidth * pressure * (1 - thinning); const qreal MINIMUM_STROKE_WIDTH = 1.0; if (strokeWidth < MINIMUM_STROKE_WIDTH) { strokeWidth = MINIMUM_STROKE_WIDTH; } return strokeWidth; } qreal KarbonCalligraphyTool::calculateAngle(const QPointF &oldSpeed, const QPointF &newSpeed) { // calculate the average of the speed (sum of the normalized values) qreal oldLength = QLineF(QPointF(0, 0), oldSpeed).length(); qreal newLength = QLineF(QPointF(0, 0), newSpeed).length(); QPointF oldSpeedNorm = !qFuzzyCompare(oldLength + 1, 1) ? oldSpeed / oldLength : QPointF(0, 0); QPointF newSpeedNorm = !qFuzzyCompare(newLength + 1, 1) ? newSpeed / newLength : QPointF(0, 0); QPointF speed = oldSpeedNorm + newSpeedNorm; // angle solely based on the speed qreal speedAngle = 0; if (speed.x() != 0) { // avoid division by zero speedAngle = std::atan(speed.y() / speed.x()); } else if (speed.y() > 0) { // x == 0 && y != 0 speedAngle = M_PI / 2; } else if (speed.y() < 0) { // x == 0 && y != 0 speedAngle = -M_PI / 2; } if (speed.x() < 0) { speedAngle += M_PI; } // move 90 degrees speedAngle += M_PI / 2; qreal fixedAngle = m_angle; // check if the fixed angle needs to be flipped qreal diff = fixedAngle - speedAngle; while (diff >= M_PI) { // normalize diff between -180 and 180 diff -= 2 * M_PI; } while (diff < -M_PI) { diff += 2 * M_PI; } if (std::abs(diff) > M_PI / 2) { // if absolute value < 90 fixedAngle += M_PI; // += 180 } qreal dAngle = speedAngle - fixedAngle; // normalize dAngle between -90 and +90 while (dAngle >= M_PI / 2) { dAngle -= M_PI; } while (dAngle < -M_PI / 2) { dAngle += M_PI; } qreal angle = fixedAngle + dAngle * (1.0 - m_fixation); return angle; } void KarbonCalligraphyTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); + if (!m_widget) { + createOptionWidgets(); + } + QAction *a = action("calligraphy_increase_width"); connect(a, SIGNAL(triggered()), m_widget, SLOT(increaseWidth()), Qt::UniqueConnection); a = action("calligraphy_decrease_width"); connect(a, SIGNAL(triggered()), m_widget, SLOT(decreaseWidth()), Qt::UniqueConnection); a = action("calligraphy_increase_angle"); connect(a, SIGNAL(triggered()), m_widget, SLOT(increaseAngle()), Qt::UniqueConnection); a = action("calligraphy_decrease_angle"); connect(a, SIGNAL(triggered()), m_widget, SLOT(decreaseAngle()), Qt::UniqueConnection); useCursor(Qt::CrossCursor); m_lastShape = 0; } void KarbonCalligraphyTool::deactivate() { QAction *a = action("calligraphy_increase_width"); disconnect(a, 0, this, 0); a = action("calligraphy_decrease_width"); disconnect(a, 0, this, 0); a = action("calligraphy_increase_angle"); disconnect(a, 0, this, 0); a = action("calligraphy_decrease_angle"); disconnect(a, 0, this, 0); if (m_lastShape && canvas()->shapeManager()->shapes().contains(m_lastShape)) { KoSelection *selection = canvas()->shapeManager()->selection(); selection->deselectAll(); selection->select(m_lastShape); } KoToolBase::deactivate(); } QList > KarbonCalligraphyTool::createOptionWidgets() { // if the widget don't exists yet create it QList > widgets; //KoFillConfigWidget *fillWidget = new KoFillConfigWidget(0); //fillWidget->setWindowTitle(i18n("Fill")); //widgets.append(fillWidget); m_widget = new KarbonCalligraphyOptionWidget(); connect(m_widget, SIGNAL(usePathChanged(bool)), this, SLOT(setUsePath(bool))); connect(m_widget, SIGNAL(usePressureChanged(bool)), this, SLOT(setUsePressure(bool))); connect(m_widget, SIGNAL(useAngleChanged(bool)), this, SLOT(setUseAngle(bool))); connect(m_widget, SIGNAL(widthChanged(double)), this, SLOT(setStrokeWidth(double))); connect(m_widget, SIGNAL(thinningChanged(double)), this, SLOT(setThinning(double))); connect(m_widget, SIGNAL(angleChanged(int)), this, SLOT(setAngle(int))); connect(m_widget, SIGNAL(fixationChanged(double)), this, SLOT(setFixation(double))); connect(m_widget, SIGNAL(capsChanged(double)), this, SLOT(setCaps(double))); connect(m_widget, SIGNAL(massChanged(double)), this, SLOT(setMass(double))); connect(m_widget, SIGNAL(dragChanged(double)), this, SLOT(setDrag(double))); connect(this, SIGNAL(pathSelectedChanged(bool)), m_widget, SLOT(setUsePathEnabled(bool))); // sync all parameters with the loaded profile m_widget->emitAll(); m_widget->setObjectName(i18n("Calligraphy")); m_widget->setWindowTitle(i18n("Calligraphy")); widgets.append(m_widget); return widgets; } void KarbonCalligraphyTool::setStrokeWidth(double width) { m_strokeWidth = width; } void KarbonCalligraphyTool::setThinning(double thinning) { m_thinning = thinning; } void KarbonCalligraphyTool::setAngle(int angle) { m_customAngle = angle; } void KarbonCalligraphyTool::setFixation(double fixation) { m_fixation = fixation; } void KarbonCalligraphyTool::setMass(double mass) { m_mass = mass * mass + 1; } void KarbonCalligraphyTool::setDrag(double drag) { m_drag = drag; } void KarbonCalligraphyTool::setUsePath(bool usePath) { m_usePath = usePath; } void KarbonCalligraphyTool::setUsePressure(bool usePressure) { m_usePressure = usePressure; } void KarbonCalligraphyTool::setUseAngle(bool useAngle) { m_useAngle = useAngle; } void KarbonCalligraphyTool::setCaps(double caps) { m_caps = caps; } void KarbonCalligraphyTool::updateSelectedPath() { KoPathShape *oldSelectedPath = m_selectedPath; // save old value KoSelection *selection = canvas()->shapeManager()->selection(); if (selection) { // null pointer if it the selection isn't a KoPathShape // or if the selection is empty m_selectedPath = dynamic_cast(selection->firstSelectedShape()); // or if it's a KoPathShape but with no or more than one subpaths if (m_selectedPath && m_selectedPath->subpathCount() != 1) { m_selectedPath = 0; } // or if there ora none or more than 1 shapes selected if (selection->count() != 1) { m_selectedPath = 0; } // emit signal it there wasn't a selected path and now there is // or the other way around if ((m_selectedPath != 0) != (oldSelectedPath != 0)) { emit pathSelectedChanged(m_selectedPath != 0); } } } diff --git a/sdk/tests/filestest.h b/sdk/tests/filestest.h index 014cff7a6d..940ebee00f 100644 --- a/sdk/tests/filestest.h +++ b/sdk/tests/filestest.h @@ -1,118 +1,299 @@ /* * Copyright (C) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef FILESTEST #define FILESTEST #include "testutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include +#include +#include +#include namespace TestUtil { -void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0, int maxNumFailingPixels = 0) +void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0, int maxNumFailingPixels = 0, bool showDebug = true) { QDir dirSources(_dirname); QStringList failuresFileInfo; QStringList failuresDocImage; QStringList failuresCompare; Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { qDebug() << sourceFileInfo.fileName(); if (exclusions.indexOf(sourceFileInfo.fileName()) > -1) { continue; } if (!sourceFileInfo.isHidden() && !sourceFileInfo.isDir()) { QFileInfo resultFileInfo(QString(FILES_DATA_DIR) + "/results/" + sourceFileInfo.fileName() + resultSuffix + ".png"); if (!resultFileInfo.exists()) { failuresFileInfo << resultFileInfo.fileName(); continue; } KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); doc->setFileBatchMode(true); - KisImportExportFilter::ConversionStatus status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); + KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); Q_UNUSED(status); if (!doc->image()) { failuresDocImage << sourceFileInfo.fileName(); continue; } QString id = doc->image()->colorSpace()->id(); if (id != "GRAYA" && id != "GRAYAU16" && id != "RGBA" && id != "RGBA16") { dbgKrita << "Images need conversion"; doc->image()->convertImageColorSpace(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::NoOptimization); } qApp->processEvents(); doc->image()->waitForDone(); QImage sourceImage = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); QImage resultImage(resultFileInfo.absoluteFilePath()); resultImage = resultImage.convertToFormat(QImage::Format_ARGB32); sourceImage = sourceImage.convertToFormat(QImage::Format_ARGB32); QPoint pt; - if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy, fuzzy, maxNumFailingPixels)) { + if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy, fuzzy, maxNumFailingPixels, showDebug)) { failuresCompare << sourceFileInfo.fileName() + ": " + QString("Pixel (%1,%2) has different values").arg(pt.x()).arg(pt.y()).toLatin1(); sourceImage.save(sourceFileInfo.fileName() + ".png"); resultImage.save(resultFileInfo.fileName() + ".expected.png"); continue; } delete doc; } } if (failuresCompare.isEmpty() && failuresDocImage.isEmpty() && failuresFileInfo.isEmpty()) { return; } qWarning() << "Comparison failures: " << failuresCompare; qWarning() << "No image failures: " << failuresDocImage; qWarning() << "No comparison image: " << failuresFileInfo; QFAIL("Failed testing files"); } + +void prepareFile(QFileInfo sourceFileInfo, bool removePermissionToWrite, bool removePermissionToRead) +{ + + QFileDevice::Permissions permissionsBefore; + if (sourceFileInfo.exists()) { + permissionsBefore = QFile::permissions(sourceFileInfo.absoluteFilePath()); + ENTER_FUNCTION() << permissionsBefore; + } else { + QFile file(sourceFileInfo.absoluteFilePath()); + bool opened = file.open(QIODevice::ReadWrite); + if (!opened) { + qDebug() << "The file cannot be opened/created: " << file.error() << file.errorString(); + } + permissionsBefore = file.permissions(); + file.close(); + } + QFileDevice::Permissions permissionsNow = permissionsBefore; + if (removePermissionToRead) { + permissionsNow = permissionsBefore & + (~QFileDevice::ReadUser & ~QFileDevice::ReadOwner + & ~QFileDevice::ReadGroup & ~QFileDevice::ReadOther); + } + if (removePermissionToWrite) { + permissionsNow = permissionsBefore & + (~QFileDevice::WriteUser & ~QFileDevice::WriteOwner + & ~QFileDevice::WriteGroup & ~QFileDevice::WriteOther); + } + + QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsNow); + +} + +void restorePermissionsToReadAndWrite(QFileInfo sourceFileInfo) +{ + QFileDevice::Permissions permissionsNow = sourceFileInfo.permissions(); + QFileDevice::Permissions permissionsAfter = permissionsNow + | (QFileDevice::ReadUser | QFileDevice::ReadOwner + | QFileDevice::ReadGroup | QFileDevice::ReadOther) + | (QFileDevice::WriteUser | QFileDevice::WriteOwner + | QFileDevice::WriteGroup | QFileDevice::WriteOther); + QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsAfter); +} + + +void testImportFromWriteonly(const QString& _dirname, QString mimetype = "") +{ + QString writeonlyFilename = _dirname + "writeonlyFile.txt"; + QFileInfo sourceFileInfo(writeonlyFilename); + + prepareFile(sourceFileInfo, false, true); + + KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); + + KisImportExportManager manager(doc); + doc->setFileBatchMode(true); + + KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype); + qDebug() << "import result = " << status; + + QString failMessage = ""; + bool fail = false; + + if (status == ImportExportCodes::FileFormatIncorrect) { + qDebug() << "Make sure you set the correct mimetype in the test case."; + failMessage = "Incorrect status."; + fail = true; + } + + qApp->processEvents(); + + if (doc->image()) { + doc->image()->waitForDone(); + } + + delete doc; + + restorePermissionsToReadAndWrite(sourceFileInfo); + + QVERIFY(!status.isOk()); + if (fail) { + QFAIL(failMessage.toUtf8()); + } + +} + + +void testExportToReadonly(const QString& _dirname, QString mimetype = "", bool useDocumentExport=false) +{ + QString readonlyFilename = _dirname + "readonlyFile.txt"; + + QFileInfo sourceFileInfo(readonlyFilename); + prepareFile(sourceFileInfo, true, false); + + KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); + + KisImportExportManager manager(doc); + doc->setFileBatchMode(true); + + KisImportExportErrorCode status = ImportExportCodes::OK; + QString failMessage = ""; + bool fail = false; + + { + MaskParent p; + ENTER_FUNCTION() << doc->image(); + + doc->setCurrentImage(p.image); + + if (useDocumentExport) { + bool result = doc->exportDocumentSync(QUrl(sourceFileInfo.absoluteFilePath()), mimetype.toUtf8()); + status = result ? ImportExportCodes::OK : ImportExportCodes::Failure; + } else { + status = manager.exportDocument(sourceFileInfo.absoluteFilePath(), sourceFileInfo.absoluteFilePath(), mimetype.toUtf8()); + } + + qDebug() << "export result = " << status; + + if (!useDocumentExport && status == ImportExportCodes::FileFormatIncorrect) { + qDebug() << "Make sure you set the correct mimetype in the test case."; + failMessage = "Incorrect status."; + fail = true; + } + + + qApp->processEvents(); + + if (doc->image()) { + doc->image()->waitForDone(); + } + + } + delete doc; + + restorePermissionsToReadAndWrite(sourceFileInfo); + + QVERIFY(!status.isOk()); + if (fail) { + QFAIL(failMessage.toUtf8()); + } +} + + + +void testImportIncorrectFormat(const QString& _dirname, QString mimetype = "") +{ + QString incorrectFormatFilename = _dirname + "incorrectFormatFile.txt"; + QFileInfo sourceFileInfo(incorrectFormatFilename); + + prepareFile(sourceFileInfo, false, false); + + KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); + + KisImportExportManager manager(doc); + doc->setFileBatchMode(true); + + KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype); + qDebug() << "import result = " << status; + + + qApp->processEvents(); + + if (doc->image()) { + doc->image()->waitForDone(); + } + + delete doc; + + QVERIFY(!status.isOk()); + QVERIFY(status == KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect) + || status == KisImportExportErrorCode(ImportExportCodes::ErrorWhileReading)); // in case the filter doesn't know if it can't read or just parse + +} + + + + + } #endif diff --git a/sdk/tests/qimage_test_util.h b/sdk/tests/qimage_test_util.h index e1d0a294ce..fffbb596fc 100644 --- a/sdk/tests/qimage_test_util.h +++ b/sdk/tests/qimage_test_util.h @@ -1,245 +1,246 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef QIMAGE_TEST_UTIL_H #define QIMAGE_TEST_UTIL_H #ifdef FILES_OUTPUT_DIR #include #include namespace TestUtil { inline QString fetchExternalDataFileName(const QString relativeFileName) { static QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); static QString unittestsDataDirPath = "KRITA_UNITTESTS_DATA_DIR"; QString path; if (!env.contains(unittestsDataDirPath)) { warnKrita << "Environment variable" << unittestsDataDirPath << "is not set"; return QString(); } else { path = env.value(unittestsDataDirPath, ""); } QString filename = path + QDir::separator() + relativeFileName; return filename; } inline QString fetchDataFileLazy(const QString relativeFileName, bool externalTest = false) { if (externalTest) { return fetchExternalDataFileName(relativeFileName); } else { QString filename = QString(FILES_DATA_DIR) + QDir::separator() + relativeFileName; if (QFileInfo(filename).exists()) { return filename; } filename = QString(FILES_DEFAULT_DATA_DIR) + QDir::separator() + relativeFileName; if (QFileInfo(filename).exists()) { return filename; } } return QString(); } // quint8 arguments are automatically converted into int inline bool compareChannels(int ch1, int ch2, int fuzzy) { return qAbs(ch1 - ch2) <= fuzzy; } -inline bool compareQImages(QPoint & pt, const QImage & image1, const QImage & image2, int fuzzy = 0, int fuzzyAlpha = 0, int maxNumFailingPixels = 0) +inline bool compareQImages(QPoint & pt, const QImage & image1, const QImage & image2, int fuzzy = 0, int fuzzyAlpha = 0, int maxNumFailingPixels = 0, bool showDebug = true) { // QTime t; // t.start(); const int w1 = image1.width(); const int h1 = image1.height(); const int w2 = image2.width(); const int h2 = image2.height(); const int bytesPerLine = image1.bytesPerLine(); if (w1 != w2 || h1 != h2) { pt.setX(-1); pt.setY(-1); qDebug() << "Images have different sizes" << image1.size() << image2.size(); return false; } int numFailingPixels = 0; for (int y = 0; y < h1; ++y) { const QRgb * const firstLine = reinterpret_cast(image2.scanLine(y)); const QRgb * const secondLine = reinterpret_cast(image1.scanLine(y)); if (memcmp(firstLine, secondLine, bytesPerLine) != 0) { for (int x = 0; x < w1; ++x) { const QRgb a = firstLine[x]; const QRgb b = secondLine[x]; const bool same = compareChannels(qRed(a), qRed(b), fuzzy) && compareChannels(qGreen(a), qGreen(b), fuzzy) && compareChannels(qBlue(a), qBlue(b), fuzzy); const bool sameAlpha = compareChannels(qAlpha(a), qAlpha(b), fuzzyAlpha); const bool bothTransparent = sameAlpha && qAlpha(a)==0; if (!bothTransparent && (!same || !sameAlpha)) { pt.setX(x); pt.setY(y); numFailingPixels++; - qDebug() << " Different at" << pt - << "source" << qRed(a) << qGreen(a) << qBlue(a) << qAlpha(a) - << "dest" << qRed(b) << qGreen(b) << qBlue(b) << qAlpha(b) - << "fuzzy" << fuzzy - << "fuzzyAlpha" << fuzzyAlpha - << "(" << numFailingPixels << "of" << maxNumFailingPixels << "allowed )"; - + if (showDebug) { + qDebug() << " Different at" << pt + << "source" << qRed(a) << qGreen(a) << qBlue(a) << qAlpha(a) + << "dest" << qRed(b) << qGreen(b) << qBlue(b) << qAlpha(b) + << "fuzzy" << fuzzy + << "fuzzyAlpha" << fuzzyAlpha + << "(" << numFailingPixels << "of" << maxNumFailingPixels << "allowed )"; + } if (numFailingPixels > maxNumFailingPixels) { return false; } } } } } // qDebug() << "compareQImages time elapsed:" << t.elapsed(); // qDebug() << "Images are identical"; return true; } inline bool checkQImageImpl(bool externalTest, const QImage &srcImage, const QString &testName, const QString &prefix, const QString &name, int fuzzy, int fuzzyAlpha, int maxNumFailingPixels) { QImage image = srcImage.convertToFormat(QImage::Format_ARGB32); if (fuzzyAlpha == -1) { fuzzyAlpha = fuzzy; } QString filename(prefix + "_" + name + ".png"); QString dumpName(prefix + "_" + name + "_expected.png"); const QString standardPath = testName + QDir::separator() + prefix + QDir::separator() + filename; QString fullPath = fetchDataFileLazy(standardPath, externalTest); if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) { // Try without the testname subdirectory fullPath = fetchDataFileLazy(prefix + QDir::separator() + filename, externalTest); } if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) { // Try without the prefix subdirectory fullPath = fetchDataFileLazy(testName + QDir::separator() + filename, externalTest); } if (!QFileInfo(fullPath).exists()) { fullPath = ""; } bool canSkipExternalTest = fullPath.isEmpty() && externalTest; QImage ref(fullPath); bool valid = true; QPoint t; if(!compareQImages(t, image, ref, fuzzy, fuzzyAlpha, maxNumFailingPixels)) { bool saveStandardResults = true; if (canSkipExternalTest) { static QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); static QString writeUnittestsVar = "KRITA_WRITE_UNITTESTS"; int writeUnittests = env.value(writeUnittestsVar, "0").toInt(); if (writeUnittests) { QString path = fetchExternalDataFileName(standardPath); QFileInfo pathInfo(path); QDir directory; directory.mkpath(pathInfo.path()); qDebug() << "--- Saving reference image:" << name << path; image.save(path); saveStandardResults = false; } else { qDebug() << "--- External image not found. Skipping..." << name; } } else { qDebug() << "--- Wrong image:" << name; valid = false; } if (saveStandardResults) { image.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + filename); ref.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + dumpName); } } return valid; } inline bool checkQImage(const QImage &image, const QString &testName, const QString &prefix, const QString &name, int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0) { return checkQImageImpl(false, image, testName, prefix, name, fuzzy, fuzzyAlpha, maxNumFailingPixels); } inline bool checkQImageExternal(const QImage &image, const QString &testName, const QString &prefix, const QString &name, int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0) { return checkQImageImpl(true, image, testName, prefix, name, fuzzy, fuzzyAlpha, maxNumFailingPixels); } } #endif // FILES_OUTPUT_DIR #endif // QIMAGE_TEST_UTIL_H