File indexing completed on 2024-05-12 16:01:31

0001 /*
0002  *  SPDX-FileCopyrightText: 2013 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 #include "kis_file_layer.h"
0007 
0008 #include <QFile>
0009 #include <QFileInfo>
0010 
0011 #include "kis_transform_worker.h"
0012 #include "kis_filter_strategy.h"
0013 #include "kis_node_progress_proxy.h"
0014 #include "kis_node_visitor.h"
0015 #include "kis_image.h"
0016 #include "kis_types.h"
0017 #include "commands_new/kis_node_move_command2.h"
0018 #include "kis_default_bounds.h"
0019 #include "kis_layer_properties_icons.h"
0020 #include <KisPart.h>
0021 #include <KisDocument.h>
0022 #include <QDir>
0023 
0024 
0025 KisFileLayer::KisFileLayer(KisImageWSP image, const QString &name, quint8 opacity)
0026     : KisExternalLayer(image, name, opacity)
0027 {
0028     /**
0029      * Set default paint device for a layer. It will be used in case
0030      * the file does not exist anymore. Or course, this can happen only
0031      * in the failing execution path.
0032      */
0033     m_paintDevice = new KisPaintDevice(image->colorSpace());
0034     m_paintDevice->setDefaultBounds(new KisDefaultBounds(image));
0035 
0036     connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,qreal,qreal,QSize)), SLOT(slotLoadingFinished(KisPaintDeviceSP,qreal,qreal,QSize)));
0037     connect(&m_loader, SIGNAL(loadingFailed()), SLOT(slotLoadingFailed()));
0038     connect(&m_loader, SIGNAL(fileExistsStateChanged(bool)), SLOT(slotFileExistsStateChanged(bool)));
0039     connect(this, SIGNAL(sigRequestOpenFile()), SLOT(openFile()));
0040 }
0041 
0042 KisFileLayer::KisFileLayer(KisImageWSP image, const QString &basePath, const QString &filename, ScalingMethod scaleToImageResolution, const QString &name, quint8 opacity, const KoColorSpace *fallbackColorSpace)
0043     : KisExternalLayer(image, name, opacity)
0044     , m_basePath(basePath)
0045     , m_filename(filename)
0046     , m_scalingMethod(scaleToImageResolution)
0047 {
0048     /**
0049      * Set default paint device for a layer. It will be used in case
0050      * the file does not exist anymore. Or course, this can happen only
0051      * in the failing execution path.
0052      */
0053     m_paintDevice = new KisPaintDevice(fallbackColorSpace ? fallbackColorSpace : image->colorSpace());
0054     m_paintDevice->setDefaultBounds(new KisDefaultBounds(image));
0055 
0056     connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,qreal,qreal,QSize)), SLOT(slotLoadingFinished(KisPaintDeviceSP,qreal,qreal,QSize)));
0057     connect(&m_loader, SIGNAL(loadingFailed()), SLOT(slotLoadingFailed()));
0058     connect(&m_loader, SIGNAL(fileExistsStateChanged(bool)), SLOT(slotFileExistsStateChanged(bool)));
0059     connect(this, SIGNAL(sigRequestOpenFile()), SLOT(openFile()));
0060 
0061     QFileInfo fi(path());
0062     if (fi.exists()) {
0063         m_loader.setPath(path());
0064         m_loader.reloadImage();
0065     }
0066 }
0067 
0068 KisFileLayer::~KisFileLayer()
0069 {
0070 }
0071 
0072 KisFileLayer::KisFileLayer(const KisFileLayer &rhs)
0073     : KisExternalLayer(rhs)
0074 {
0075     m_basePath = rhs.m_basePath;
0076     m_filename = rhs.m_filename;
0077     m_scalingMethod = rhs.m_scalingMethod;
0078 
0079     m_generatedForImageSize = rhs.m_generatedForImageSize;
0080     m_generatedForXRes = rhs.m_generatedForXRes;
0081     m_generatedForYRes = rhs.m_generatedForYRes;
0082     m_state = rhs.m_state;
0083 
0084     m_paintDevice = new KisPaintDevice(*rhs.m_paintDevice);
0085 
0086     connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,qreal,qreal,QSize)), SLOT(slotLoadingFinished(KisPaintDeviceSP,qreal,qreal,QSize)));
0087     connect(this, SIGNAL(sigRequestOpenFile()), SLOT(openFile()));
0088     m_loader.setPath(path());
0089 }
0090 
0091 QIcon KisFileLayer::icon() const
0092 {
0093     return KisIconUtils::loadIcon("fileLayer");
0094 }
0095 
0096 void KisFileLayer::resetCache()
0097 {
0098     m_loader.reloadImage();
0099 }
0100 
0101 KisPaintDeviceSP KisFileLayer::original() const
0102 {
0103     return m_paintDevice;
0104 }
0105 
0106 KisPaintDeviceSP KisFileLayer::paintDevice() const
0107 {
0108     return 0;
0109 }
0110 
0111 void KisFileLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties)
0112 {
0113     KisBaseNode::setSectionModelProperties(properties);
0114     Q_FOREACH (const KisBaseNode::Property &property, properties) {
0115         if (property.id== KisLayerPropertiesIcons::openFileLayerFile.id()) {
0116             if (property.state.toBool() == false) {
0117                 Q_EMIT sigRequestOpenFile();
0118             }
0119         }
0120     }
0121 }
0122 
0123 KisBaseNode::PropertyList KisFileLayer::sectionModelProperties() const
0124 {
0125     KisBaseNode::PropertyList l = KisLayer::sectionModelProperties();
0126     l << KisBaseNode::Property(KoID("sourcefile", i18n("File")), m_filename);
0127     l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::openFileLayerFile, true);
0128 
0129     auto fileNameOrPlaceholder =
0130     [this] () {
0131         return !m_filename.isEmpty() ? m_filename : i18nc("placeholder test for a warning when not file is set in the file layer", "<No file name is set>");
0132     };
0133 
0134     if (m_state == FileNotFound) {
0135         l << KisLayerPropertiesIcons::getErrorProperty(i18nc("a tooltip shown when a file layer cannot find its linked file",
0136                                                              "Linked file not found: %1", fileNameOrPlaceholder()));
0137     } else if (m_state == FileLoadingFailed) {
0138         l << KisLayerPropertiesIcons::getErrorProperty(i18nc("a tooltip shown when a file layer cannot load its linked file",
0139                                                              "Failed to load linked file: %1", fileNameOrPlaceholder()));
0140     }
0141     return l;
0142 }
0143 
0144 void KisFileLayer::setFileName(const QString &basePath, const QString &filename)
0145 {
0146     m_basePath = basePath;
0147     m_filename = filename;
0148     QFileInfo fi(path());
0149     if (fi.exists()) {
0150         m_loader.setPath(path());
0151         m_loader.reloadImage();
0152     }
0153 }
0154 
0155 QString KisFileLayer::fileName() const
0156 {
0157     return m_filename;
0158 }
0159 
0160 QString KisFileLayer::path() const
0161 {
0162     if (m_basePath.isEmpty()) {
0163         return m_filename;
0164     }
0165     else {
0166 #ifndef Q_OS_ANDROID
0167         return QDir(m_basePath).filePath(QDir::cleanPath(m_filename));
0168 #else
0169         return m_filename;
0170 #endif
0171     }
0172 }
0173 
0174 void KisFileLayer::openFile() const
0175 {
0176     bool fileAlreadyOpen = false;
0177     Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) {
0178         if (doc->path()==path()){
0179             fileAlreadyOpen = true;
0180         }
0181     }
0182     if (!fileAlreadyOpen && QFile::exists(QFileInfo(path()).absoluteFilePath())) {
0183         KisPart::instance()->openExistingFile(QFileInfo(path()).absoluteFilePath());
0184     }
0185 }
0186 
0187 void KisFileLayer::changeState(State newState)
0188 {
0189     const State oldState = m_state;
0190     m_state = newState;
0191     if (oldState != newState) {
0192         baseNodeChangedCallback();
0193     }
0194 }
0195 
0196 KisFileLayer::ScalingMethod KisFileLayer::scalingMethod() const
0197 {
0198     return m_scalingMethod;
0199 }
0200 
0201 void KisFileLayer::setScalingMethod(ScalingMethod method)
0202 {
0203     m_scalingMethod = method;
0204 }
0205 
0206 void KisFileLayer::slotLoadingFinished(KisPaintDeviceSP projection,
0207                                        qreal xRes, qreal yRes,
0208                                        const QSize &size)
0209 {
0210     qint32 oldX = x();
0211     qint32 oldY = y();
0212     const QRect oldLayerExtent = m_paintDevice->extent();
0213 
0214 
0215     m_paintDevice->makeCloneFrom(projection, projection->extent());
0216     m_paintDevice->setDefaultBounds(new KisDefaultBounds(image()));
0217 
0218     /**
0219      * This method can be transitively called from KisFileLayer::setImage(),
0220      * which, in turn, can be called from the KisImage's copy-ctor. The shared
0221      * pointer is, obviously, not initialized during construction, therefore
0222      * upgrading our constructor to a strong pointer will cause a crash.
0223      *
0224      * Therefore, we use a weak pointer here. It is extremely dangerous, but
0225      * since this method is usually called from the GUI thread synchronously
0226      * it should be "somewhat safe".
0227      */
0228     KisImageWSP image = this->image();
0229     if (image) {
0230         if (m_scalingMethod == ToImagePPI &&
0231                 (!qFuzzyCompare(image->xRes(), xRes) ||
0232                  !qFuzzyCompare(image->yRes(), yRes))) {
0233 
0234             qreal xscale = image->xRes() / xRes;
0235             qreal yscale = image->yRes() / yRes;
0236 
0237             KisTransformWorker worker(m_paintDevice, xscale, yscale, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, KisFilterStrategyRegistry::instance()->get("Bicubic"));
0238             worker.run();
0239         }
0240         else if (m_scalingMethod == ToImageSize && size != image->size()) {
0241             QSize sz = size;
0242             sz.scale(image->size(), Qt::KeepAspectRatio);
0243             qreal xscale =  (qreal)sz.width() / (qreal)size.width();
0244             qreal yscale = (qreal)sz.height() / (qreal)size.height();
0245 
0246             KisTransformWorker worker(m_paintDevice, xscale, yscale, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, KisFilterStrategyRegistry::instance()->get("Bicubic"));
0247             worker.run();
0248         }
0249 
0250         m_generatedForImageSize = image->size();
0251         m_generatedForXRes = image->xRes();
0252         m_generatedForYRes = image->yRes();
0253     }
0254 
0255     m_paintDevice->setX(oldX);
0256     m_paintDevice->setY(oldY);
0257 
0258     changeState(FileLoaded);
0259     setDirty(m_paintDevice->extent() | oldLayerExtent);
0260 }
0261 
0262 void KisFileLayer::slotLoadingFailed()
0263 {
0264     changeState(FileLoadingFailed);
0265 }
0266 
0267 void KisFileLayer::slotFileExistsStateChanged(bool exists)
0268 {
0269     changeState(exists ? FileLoaded : FileNotFound);
0270 }
0271 
0272 KisNodeSP KisFileLayer::clone() const
0273 {
0274     return KisNodeSP(new KisFileLayer(*this));
0275 }
0276 
0277 bool KisFileLayer::allowAsChild(KisNodeSP node) const
0278 {
0279     return node->inherits("KisMask");
0280 }
0281 
0282 bool KisFileLayer::accept(KisNodeVisitor& visitor)
0283 {
0284     return visitor.visit(this);
0285 }
0286 
0287 void KisFileLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
0288 {
0289     return visitor.visit(this, undoAdapter);
0290 }
0291 
0292 KUndo2Command* KisFileLayer::crop(const QRect & rect)
0293 {
0294     QPoint oldPos(x(), y());
0295     QPoint newPos = oldPos - rect.topLeft();
0296 
0297     return new KisNodeMoveCommand2(this, oldPos, newPos);
0298 }
0299 
0300 KUndo2Command* KisFileLayer::transform(const QTransform &/*transform*/)
0301 {
0302     warnKrita << "WARNING: File Layer does not support transformations!" << name();
0303     return 0;
0304 }
0305 
0306 void KisFileLayer::setImage(KisImageWSP image)
0307 {
0308     KisImageWSP oldImage = this->image();
0309 
0310     m_paintDevice->setDefaultBounds(new KisDefaultBounds(image));
0311     KisExternalLayer::setImage(image);
0312 
0313     if (m_scalingMethod != None && image && oldImage != image) {
0314         bool canSkipReloading = false;
0315 
0316         if (m_scalingMethod == ToImageSize && image && image->size() == m_generatedForImageSize) {
0317             canSkipReloading = true;
0318         }
0319 
0320         if (m_scalingMethod == ToImagePPI && image &&
0321                 qFuzzyCompare(image->xRes(), m_generatedForXRes) &&
0322                 qFuzzyCompare(image->yRes(), m_generatedForYRes)) {
0323 
0324             canSkipReloading = true;
0325         }
0326 
0327         if (!canSkipReloading) {
0328             m_loader.reloadImage();
0329         }
0330     }
0331 }
0332