File indexing completed on 2024-05-19 04:28:52

0001 /*
0002  *  SPDX-FileCopyrightText: 2015 Jouni Pentikäinen <joupent@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_animation_importer.h"
0008 
0009 #include <QStatusBar>
0010 
0011 #include "KoColorSpace.h"
0012 #include <KoUpdater.h>
0013 #include <QApplication>
0014 #include <QQueue>
0015 #include "KisPart.h"
0016 #include "KisDocument.h"
0017 #include "kis_image.h"
0018 #include "kis_undo_adapter.h"
0019 #include "kis_paint_layer.h"
0020 #include "kis_group_layer.h"
0021 #include "kis_raster_keyframe_channel.h"
0022 #include "kis_assign_profile_processing_visitor.h"
0023 #include "commands/kis_image_layer_add_command.h"
0024 #include <QRegExp>
0025 
0026 struct KisAnimationImporter::Private
0027 {
0028     KisImageSP image;
0029     KisDocument *document;
0030     bool stop;
0031     KoUpdaterPtr updater;
0032 };
0033 
0034 KisAnimationImporter::KisAnimationImporter(KisImageSP image, KoUpdaterPtr updater)
0035     : m_d(new Private())
0036 {
0037     m_d->document = 0;
0038     m_d->image = image;
0039     m_d->stop = false;
0040     m_d->updater = updater;
0041 }
0042 
0043 KisAnimationImporter::KisAnimationImporter(KisDocument* document)
0044     : m_d(new Private())
0045 {
0046     m_d->document= document;
0047     m_d->image = document->image();
0048     m_d->stop = false;
0049 }
0050 
0051 KisAnimationImporter::~KisAnimationImporter()
0052 {}
0053 
0054 KisImportExportErrorCode KisAnimationImporter::import(QStringList files, int firstFrame, int step, bool autoAddHoldframes, bool startfrom0, int isAscending, bool assignDocumentProfile, QList<int> optionalKeyframeTimeList)
0055 {
0056     //TODO: We should clean up this code --
0057     // There are a lot of actions here that we should break into individual methods
0058     // so that we can better control code flow, and I'd prefer to use multiple import
0059     // calls to better handle all of these different options!
0060     // Additionally, we might prefer to use flags for multiple booleans to improve
0061     // legibility of calls.
0062     Q_ASSERT(step > 0);
0063 
0064     KisUndoAdapter *undo = m_d->image->undoAdapter();
0065     undo->beginMacro(kundo2_i18n("Import animation"));
0066 
0067     QScopedPointer<KisDocument> importDoc(KisPart::instance()->createDocument());
0068     importDoc->setFileBatchMode(true);
0069 
0070     const bool usingPredefinedTimes = !optionalKeyframeTimeList.isEmpty() && !autoAddHoldframes;
0071     QQueue<int> predefinedFrameQueue;
0072     predefinedFrameQueue.append(optionalKeyframeTimeList);
0073 
0074     KisImportExportErrorCode status = ImportExportCodes::OK;
0075     int frame = usingPredefinedTimes ? predefinedFrameQueue.dequeue() : firstFrame;
0076     int filesProcessed = 0;
0077 
0078     if (usingPredefinedTimes) {
0079         KIS_ASSERT(files.count() == optionalKeyframeTimeList.count());
0080     }
0081 
0082     if (m_d->updater) {
0083         m_d->updater->setRange(0, files.size());
0084     }
0085 
0086     QPair<KisPaintLayerSP, KisRasterKeyframeChannel*> layerRasterChannelPair;
0087 
0088     const QRegExp rx(QLatin1String("(\\d+)"));    //regex for extracting numbers
0089     QStringList fileNumberRxList;
0090 
0091     int pos = 0;
0092 
0093     while ((pos = rx.indexIn(files.at(0), pos)) != -1) {
0094         fileNumberRxList << rx.cap(1);
0095         pos += rx.matchedLength();
0096     }
0097 
0098     int firstFrameNumber = 0;
0099     bool ok;
0100 
0101     if (!fileNumberRxList.isEmpty()) {
0102         fileNumberRxList.last().toInt(&ok);    // selects the last number of file name of the first frame (useful for descending order)
0103         // Note to self -- ^^ uh.... This isn't doing anything?? Shouldn't this assign `firstFrameNumber`?
0104     }
0105 
0106     if (firstFrameNumber == 0){
0107         startfrom0 = false;     // if enabled, the zeroth frame will be places in -1 slot, leading to an error
0108     }
0109 
0110     fileNumberRxList.clear();
0111     const int offset = (startfrom0 ? 1 : 0);    //offset added to consider file numbering starts from 1 instead of 0
0112     int autoframe = 0;
0113 
0114     KisConfig cfg(true);
0115 
0116     Q_FOREACH(QString file, files) {
0117         bool successfullyLoaded = importDoc->openPath(file, KisDocument::DontAddToRecent);
0118         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(successfullyLoaded, ImportExportCodes::InternalError);
0119 
0120         if ( (!usingPredefinedTimes && frame == firstFrame)
0121           || (usingPredefinedTimes && frame == optionalKeyframeTimeList.first()) ) {
0122              layerRasterChannelPair = initializePaintLayer(importDoc, undo);
0123         }
0124 
0125         if (m_d->updater) {
0126             if (m_d->updater->interrupted()) {
0127                 m_d->stop = true;
0128             } else {
0129                 m_d->updater->setValue(filesProcessed + 1);
0130 
0131                 // the updater doesn't call that automatically,
0132                 // it is "threaded" by default
0133                 qApp->processEvents();
0134             }
0135         }
0136 
0137         if (m_d->stop) {
0138             status = ImportExportCodes::Cancelled;
0139             break;
0140         }
0141 
0142         if (cfg.trimFramesImport()) {
0143             importDoc->image()->projection()->crop(m_d->image->bounds());
0144         }
0145         importDoc->image()->projection()->purgeDefaultPixels();
0146 
0147         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(layerRasterChannelPair.second, ImportExportCodes::InternalError);
0148 
0149         if (!autoAddHoldframes) {
0150             layerRasterChannelPair.second->importFrame(frame, importDoc->image()->projection(), NULL);    // as first frame added will go to second slot i.e #1 instead of #0
0151         } else {
0152             pos = 0;
0153 
0154             while ((pos = rx.indexIn(file, pos)) != -1) {
0155                 fileNumberRxList << rx.cap(1);
0156                 pos += rx.matchedLength();
0157             }
0158 
0159             int filenum = fileNumberRxList.last().toInt(&ok);
0160 
0161             if (isAscending == 0) {
0162                 autoframe = firstFrame + filenum - offset;
0163             } else {
0164                 autoframe = firstFrame + (firstFrameNumber - filenum); //places the first frame #0 (or #1) slot, and later frames are added as per the difference
0165             }
0166 
0167             if (ok) {
0168                 layerRasterChannelPair.second->importFrame(autoframe , importDoc->image()->projection(), NULL);
0169             } else {
0170                 // if it fails to extract a number, the next frame will simply be added to next slot
0171                 layerRasterChannelPair.second->importFrame(autoframe + 1, importDoc->image()->projection(), NULL);
0172             }
0173             fileNumberRxList.clear();
0174         }
0175 
0176         if (usingPredefinedTimes && predefinedFrameQueue.count()) {
0177             frame = predefinedFrameQueue.dequeue();
0178         } else {
0179             frame += step;
0180         }
0181 
0182         filesProcessed++;
0183     }
0184 
0185     if (layerRasterChannelPair.first && assignDocumentProfile) {
0186 
0187         if (layerRasterChannelPair.first->colorSpace()->colorModelId() == m_d->image->colorSpace()->colorModelId()) {
0188 
0189             const KoColorSpace *srcColorSpace = layerRasterChannelPair.first->colorSpace();
0190             const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(
0191                         srcColorSpace->colorModelId().id()
0192                         , srcColorSpace->colorDepthId().id()
0193                         , m_d->image->colorSpace()->profile());
0194 
0195             KisAssignProfileProcessingVisitor *visitor = new KisAssignProfileProcessingVisitor(srcColorSpace, dstColorSpace);
0196             visitor->visit(layerRasterChannelPair.first.data(), undo);
0197         }
0198     }
0199 
0200     undo->endMacro();
0201 
0202     return status;
0203 }
0204 
0205 QPair<KisPaintLayerSP, KisRasterKeyframeChannel*> KisAnimationImporter::initializePaintLayer(QScopedPointer<KisDocument>& doc, KisUndoAdapter *undoAdapter)
0206 {
0207     const KoColorSpace *cs = doc->image()->colorSpace();
0208     KisPaintLayerSP paintLayer = new KisPaintLayer(m_d->image, m_d->image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
0209     undoAdapter->addCommand(new KisImageLayerAddCommand(m_d->image, paintLayer, m_d->image->rootLayer(), m_d->image->rootLayer()->childCount()));
0210 
0211     paintLayer->enableAnimation();
0212     KisRasterKeyframeChannel* contentChannel = qobject_cast<KisRasterKeyframeChannel*>(paintLayer->getKeyframeChannel(KisKeyframeChannel::Raster.id(), true));
0213     return QPair<KisPaintLayerSP, KisRasterKeyframeChannel*>(paintLayer, contentChannel);
0214 }
0215 
0216 void KisAnimationImporter::cancel()
0217 {
0218     m_d->stop = true;
0219 }