File indexing completed on 2024-06-16 04:16:52

0001 /*
0002  *  SPDX-FileCopyrightText: 2016 Laszlo Fazekas <mneko@freemail.hu>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "csv_loader.h"
0008 
0009 #include <QDebug>
0010 #include <QApplication>
0011 
0012 #include <QFile>
0013 #include <QVector>
0014 #include <QIODevice>
0015 #include <QStatusBar>
0016 #include <QFileInfo>
0017 
0018 #include <KisPart.h>
0019 #include <KisView.h>
0020 #include <KisMainWindow.h>
0021 #include <KisDocument.h>
0022 #include <KoColorSpace.h>
0023 #include <KoColorSpaceRegistry.h>
0024 #include <KoColorModelStandardIds.h>
0025 #include <KisCursorOverrideLock.h>
0026 
0027 #include <kis_debug.h>
0028 #include <kis_image.h>
0029 #include <kis_paint_layer.h>
0030 #include <kis_raster_keyframe_channel.h>
0031 #include <kis_image_animation_interface.h>
0032 #include <kis_time_span.h>
0033 
0034 #include "csv_read_line.h"
0035 #include "csv_layer_record.h"
0036 
0037 CSVLoader::CSVLoader(KisDocument *doc, bool batchMode)
0038     : m_image(0)
0039     , m_doc(doc)
0040     , m_batchMode(batchMode)
0041     , m_stop(false)
0042 {
0043 }
0044 
0045 CSVLoader::~CSVLoader()
0046 {
0047 }
0048 
0049 KisImportExportErrorCode CSVLoader::decode(QIODevice *io, const QString &filename)
0050 {
0051     QString     field;
0052     int         idx;
0053     int         frame = 0;
0054 
0055     QString     projName;
0056     int         width = 0;
0057     int         height = 0;
0058     int         frameCount = 1;
0059     float       framerate = 24.0;
0060     float       pixelRatio = 1.0;
0061 
0062     int         projNameIdx = -1;
0063     int         widthIdx = -1;
0064     int         heightIdx = -1;
0065     int         frameCountIdx = -1;
0066     int         framerateIdx = -1;
0067     int         pixelRatioIdx = -1;
0068 
0069     QVector<CSVLayerRecord*> layers;
0070 
0071     KisCursorOverrideLock cursorLock(Qt::WaitCursor);
0072 
0073     idx = filename.lastIndexOf(QRegExp("[\\/]"));
0074     QString base = (idx == -1) ? QString() : filename.left(idx + 1); //include separator
0075     QString path = filename;
0076 
0077     if (path.right(4).toUpper() == ".CSV")
0078         path = path.left(path.size() - 4);
0079 
0080     //according to the QT docs, the slash is a universal directory separator
0081     path.append(".frames/");
0082 
0083     KisImportExportErrorCode retval = ImportExportCodes::OK;
0084 
0085     dbgFile << "pos:" << io->pos();
0086 
0087     CSVReadLine readLine;
0088     QScopedPointer<KisDocument> importDoc(KisPart::instance()->createDocument());
0089     importDoc->setInfiniteAutoSaveInterval();
0090     importDoc->setFileBatchMode(true);
0091 
0092     KisView *setView(0);
0093 
0094     if (!m_batchMode) {
0095         // TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater()
0096 
0097 //        //show the statusbar message even if no view
0098 //        Q_FOREACH (KisView* view, KisPart::instance()->views()) {
0099 //            if (view && view->document() == m_doc) {
0100 //                setView = view;
0101 //                break;
0102 //            }
0103 //        }
0104 
0105 //        if (!setView) {
0106 //            QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar();
0107 //            if (sb) {
0108 //                sb->showMessage(i18n("Loading CSV file..."));
0109 //            }
0110 //        } else {
0111 //            emit m_doc->statusBarMessage(i18n("Loading CSV file..."));
0112 //        }
0113 
0114 //        emit m_doc->sigProgress(0);
0115 //        connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
0116     }
0117     int step = 0;
0118 
0119     do {
0120         qApp->processEvents();
0121 
0122         if (m_stop) {
0123             retval = ImportExportCodes::Cancelled;
0124             break;
0125         }
0126 
0127         if ((idx = readLine.nextLine(io)) <= 0) {
0128             if ((idx < 0) ||(step < 5))
0129                 retval = ImportExportCodes::FileFormatIncorrect;
0130             break;
0131         }
0132         field = readLine.nextField(); //first field of the line
0133 
0134         if (field.isNull()) continue; //empty row
0135 
0136         switch (step) {
0137 
0138         case 0 :    //skip first row
0139             step = 1;
0140             break;
0141 
0142         case 1 :    //scene header names
0143             step = 2;
0144 
0145             for (idx = 0; !field.isNull(); idx++) {
0146                 if (field == "Project Name") {
0147                     projNameIdx = idx;
0148 
0149                 } else if (field == "Width") {
0150                     widthIdx = idx;
0151 
0152                 } else if (field == "Height") {
0153                     heightIdx = idx;
0154 
0155                 } else if (field == "Frame Count") {
0156                     frameCountIdx = idx;
0157 
0158                 } else if (field == "Frame Rate") {
0159                     framerateIdx = idx;
0160 
0161                 } else if (field == "Pixel Aspect Ratio") {
0162                     pixelRatioIdx = idx;
0163                 }
0164                 field= readLine.nextField();
0165             }
0166             break;
0167 
0168         case 2 :    //scene header values
0169             step= 3;
0170 
0171             for (idx= 0; !field.isNull(); idx++) {
0172                 if (idx == projNameIdx) {
0173                     projName = field;
0174 
0175                 } else if (idx == widthIdx) {
0176                     width = field.toInt();
0177 
0178                 } else if (idx == heightIdx) {
0179                     height = field.toInt();
0180 
0181                 } else if (idx == frameCountIdx) {
0182                     frameCount = field.toInt();
0183 
0184                     if (frameCount < 1) frameCount= 1;
0185 
0186                 } else if (idx == framerateIdx) {
0187                     framerate = field.toFloat();
0188 
0189                 } else if (idx == pixelRatioIdx) {
0190                     pixelRatio = field.toFloat();
0191 
0192                 }
0193                 field= readLine.nextField();
0194             }
0195 
0196             if ((width < 1) || (height < 1)) {
0197                retval = ImportExportCodes::Failure;
0198                break;
0199             }
0200 
0201             retval = createNewImage(width, height, pixelRatio, projName.isNull() ? filename : projName);
0202             break;
0203 
0204         case 3 :    //create level headers
0205             if (field[0] != '#') break;
0206 
0207             for (; !(field = readLine.nextField()).isNull(); ) {
0208                 CSVLayerRecord* layerRecord = new CSVLayerRecord();
0209                 layers.append(layerRecord);
0210             }
0211             readLine.rewind();
0212             field = readLine.nextField();
0213             step = 4;
0214             Q_FALLTHROUGH();
0215 
0216         case 4 :    //level header
0217 
0218             if (field == "#Layers") {
0219                 //layer name
0220                 for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
0221                     layers.at(idx)->name = field;
0222 
0223                 break;
0224             }
0225             if (field == "#Density") {
0226                 //layer opacity
0227                 for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
0228                     layers.at(idx)->density = field.toFloat();
0229 
0230                 break;
0231             }
0232             if (field == "#Blending") {
0233                 //layer blending mode
0234                 for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
0235                     layers.at(idx)->blending = field;
0236 
0237                 break;
0238             }
0239             if (field == "#Visible") {
0240                 //layer visibility
0241                 for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
0242                     layers.at(idx)->visible = field.toInt();
0243 
0244                 break;
0245             }
0246             if (field == "#Folder") {
0247                 //CSV 1.1 folder location
0248                 for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
0249                     layers.at(idx)->path = validPath(field, base);
0250 
0251                 break;
0252             }
0253             if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break;
0254 
0255             step = 5;
0256 
0257             Q_FALLTHROUGH();
0258 
0259         case 5 :    //frames
0260 
0261             if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break;
0262 
0263             for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) {
0264                 CSVLayerRecord* layer = layers.at(idx);
0265 
0266                 if (layer->last != field) {
0267                     if (!m_batchMode) {
0268                         //emit m_doc->sigProgress((frame * layers.size() + idx) * 100 /
0269                         //                        (frameCount * layers.size()));
0270                     }
0271                     retval = setLayer(layer, importDoc.data(), path);
0272                     layer->last = field;
0273                     layer->frame = frame;
0274                 }
0275             }
0276             frame++;
0277             break;
0278         }
0279     } while (retval.isOk());
0280 
0281     //finish the layers
0282 
0283     if (retval.isOk()) {
0284         if (m_image) {
0285             KisImageAnimationInterface *animation = m_image->animationInterface();
0286 
0287             if (frame > frameCount)
0288                 frameCount = frame;
0289 
0290             animation->setDocumentRange(KisTimeSpan::fromTimeToTime(0,frameCount - 1));
0291             animation->setFramerate((int)framerate);
0292         }
0293 
0294         for (idx = 0; idx < layers.size(); idx++) {
0295             CSVLayerRecord* layer = layers.at(idx);
0296             //empty layers without any pictures are dropped
0297 
0298             if ((layer->frame > 0) || !layer->last.isEmpty()) {
0299                 retval = setLayer(layer, importDoc.data(), path);
0300 
0301                 if (!retval.isOk())
0302                     break;
0303             }
0304         }
0305     }
0306 
0307     if (m_image) {
0308         //insert the existing layers by the right order
0309         for (idx = layers.size() - 1; idx >= 0; idx--) {
0310             CSVLayerRecord* layer = layers.at(idx);
0311 
0312             if (layer->layer) {
0313                 m_image->addNode(layer->layer, m_image->root());
0314             }
0315         }
0316         m_image->unlock();
0317     }
0318     qDeleteAll(layers);
0319     io->close();
0320 
0321     if (!m_batchMode) {
0322         // disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
0323         // emit m_doc->sigProgress(100);
0324 
0325         if (!setView) {
0326             QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar();
0327             if (sb) {
0328                 sb->clearMessage();
0329             }
0330         } else {
0331             emit m_doc->clearStatusBarMessage();
0332         }
0333     }
0334 
0335     return retval;
0336 }
0337 
0338 QString CSVLoader::convertBlending(const QString &blending)
0339 {
0340     if (blending == "Color") return COMPOSITE_OVER;
0341     if (blending == "Behind") return COMPOSITE_BEHIND;
0342     if (blending == "Erase") return COMPOSITE_ERASE;
0343     // "Shade"
0344     if (blending == "Light") return COMPOSITE_LINEAR_LIGHT;
0345     if (blending == "Colorize") return COMPOSITE_COLORIZE;
0346     if (blending == "Hue") return COMPOSITE_HUE;
0347     if (blending == "Add") return COMPOSITE_ADD;
0348     if (blending == "Sub") return COMPOSITE_INVERSE_SUBTRACT;
0349     if (blending == "Multiply") return COMPOSITE_MULT;
0350     if (blending == "Screen") return COMPOSITE_SCREEN;
0351     // "Replace"
0352     // "Substitute"
0353     if (blending == "Difference") return COMPOSITE_DIFF;
0354     if (blending == "Divide") return COMPOSITE_DIVIDE;
0355     if (blending == "Overlay") return COMPOSITE_OVERLAY;
0356     if (blending == "Light2") return COMPOSITE_DODGE;
0357     if (blending == "Shade2") return COMPOSITE_BURN;
0358     if (blending == "HardLight") return COMPOSITE_HARD_LIGHT;
0359     if (blending == "SoftLight") return COMPOSITE_SOFT_LIGHT_PHOTOSHOP;
0360     if (blending == "GrainExtract") return COMPOSITE_GRAIN_EXTRACT;
0361     if (blending == "GrainMerge") return COMPOSITE_GRAIN_MERGE;
0362     if (blending == "Sub2") return COMPOSITE_SUBTRACT;
0363     if (blending == "Darken") return COMPOSITE_DARKEN;
0364     if (blending == "Lighten") return COMPOSITE_LIGHTEN;
0365     if (blending == "Saturation") return COMPOSITE_SATURATION;
0366 
0367     return COMPOSITE_OVER;
0368 }
0369 
0370 QString CSVLoader::validPath(const QString &path,const QString &base)
0371 {
0372     //replace Windows directory separators with the universal /
0373 
0374     QString tryPath= QString(path).replace(QString("\\"), QString("/"));
0375     int i = tryPath.lastIndexOf("/");
0376 
0377     if (i == (tryPath.size() - 1))
0378         tryPath= tryPath.left(i); //remove the ending separator if exists
0379 
0380     if (QFileInfo(tryPath).isDir())
0381         return tryPath.append("/");
0382 
0383     QString scan(tryPath);
0384     i = -1;
0385 
0386     while ((i= (scan.lastIndexOf("/",i) - 1)) > 0) {
0387         //avoid testing if the next level will be the default xxxx.layers folder
0388 
0389         if ((i >= 6) && (scan.mid(i - 6, 7) == ".layers")) continue;
0390 
0391         tryPath= QString(base).append(scan.mid(i + 2)); //base already ending with a /
0392 
0393         if (QFileInfo(tryPath).isDir())
0394             return tryPath.append("/");
0395     }
0396     return QString(); //NULL string
0397 }
0398 
0399 KisImportExportErrorCode CSVLoader::setLayer(CSVLayerRecord* layer, KisDocument *importDoc, const QString &path)
0400 {
0401     bool result = true;
0402 
0403     if (layer->channel == 0) {
0404         //create a new document layer
0405 
0406         float opacity = layer->density;
0407 
0408         if (opacity > 1.0)
0409             opacity = 1.0;
0410         else if (opacity < 0.0)
0411             opacity = 0.0;
0412 
0413         const KoColorSpace* cs = m_image->colorSpace();
0414         const QString layerName = (layer->name).isEmpty() ? m_image->nextLayerName() : layer->name;
0415 
0416         KisPaintLayer* paintLayer = new KisPaintLayer(m_image, layerName,
0417                                                        (quint8)(opacity * OPACITY_OPAQUE_U8), cs);
0418 
0419         paintLayer->setCompositeOpId(convertBlending(layer->blending));
0420         paintLayer->setVisible(layer->visible);
0421         paintLayer->enableAnimation();
0422 
0423         layer->layer = paintLayer;
0424         layer->channel = qobject_cast<KisRasterKeyframeChannel*>
0425             (paintLayer->getKeyframeChannel(KisKeyframeChannel::Raster.id(), true));
0426     }
0427 
0428 
0429     if (!layer->last.isEmpty()) {
0430         //png image
0431         QString filename = layer->path.isNull() ? path : layer->path;
0432         filename.append(layer->last);
0433 
0434         result = importDoc->openPath(filename,
0435                                     KisDocument::DontAddToRecent);
0436         if (result)
0437             layer->channel->importFrame(layer->frame, importDoc->image()->projection(), nullptr);
0438 
0439     } else {
0440         //blank
0441         layer->channel->addKeyframe(layer->frame);
0442     }
0443     return (result) ? ImportExportCodes::OK : ImportExportCodes::Failure;
0444 }
0445 
0446 KisImportExportErrorCode CSVLoader::createNewImage(int width, int height, float ratio, const QString &name)
0447 {
0448     //the CSV is RGBA 8bits, sRGB
0449 
0450     if (!m_image) {
0451         const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(
0452                                 RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), 0);
0453 
0454         if (cs) m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, name);
0455 
0456         if (!m_image) return ImportExportCodes::Failure;
0457 
0458         m_image->setResolution(ratio, 1.0);
0459         m_image->barrierLock();
0460     }
0461     return ImportExportCodes::OK;
0462 }
0463 
0464 KisImportExportErrorCode CSVLoader::buildAnimation(QIODevice *io, const QString &filename)
0465 {
0466     return decode(io, filename);
0467 }
0468 
0469 KisImageSP CSVLoader::image()
0470 {
0471     return m_image;
0472 }
0473 
0474 void CSVLoader::cancel()
0475 {
0476     m_stop = true;
0477 }