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 }