Krita Source Code Documentation
Loading...
Searching...
No Matches
csv_loader.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2016 Laszlo Fazekas <mneko@freemail.hu>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include "csv_loader.h"
8
9#include <QDebug>
10#include <QApplication>
11
12#include <QFile>
13#include <QVector>
14#include <QIODevice>
15#include <QStatusBar>
16#include <QFileInfo>
17#include <QRegularExpression>
18
19#include <KisPart.h>
20#include <KisView.h>
21#include <KisMainWindow.h>
22#include <KisDocument.h>
23#include <KoColorSpace.h>
27
28#include <kis_debug.h>
29#include <kis_image.h>
30#include <kis_paint_layer.h>
33#include <kis_time_span.h>
34
35#include "csv_read_line.h"
36#include "csv_layer_record.h"
37
38CSVLoader::CSVLoader(KisDocument *doc, bool batchMode)
39 : m_image(0)
40 , m_doc(doc)
41 , m_batchMode(batchMode)
42 , m_stop(false)
43{
44}
45
49
50KisImportExportErrorCode CSVLoader::decode(QIODevice *io, const QString &filename)
51{
52 QString field;
53 int idx;
54 int frame = 0;
55
56 QString projName;
57 int width = 0;
58 int height = 0;
59 int frameCount = 1;
60 float framerate = 24.0;
61 float pixelRatio = 1.0;
62
63 int projNameIdx = -1;
64 int widthIdx = -1;
65 int heightIdx = -1;
66 int frameCountIdx = -1;
67 int framerateIdx = -1;
68 int pixelRatioIdx = -1;
69
71
72 KisCursorOverrideLock cursorLock(Qt::WaitCursor);
73
74 idx = filename.lastIndexOf(QRegularExpression("[\\/]"));
75 QString base = (idx == -1) ? QString() : filename.left(idx + 1); //include separator
76 QString path = filename;
77
78 if (path.right(4).toUpper() == ".CSV")
79 path = path.left(path.size() - 4);
80
81 //according to the QT docs, the slash is a universal directory separator
82 path.append(".frames/");
83
85
86 dbgFile << "pos:" << io->pos();
87
88 CSVReadLine readLine;
89 QScopedPointer<KisDocument> importDoc(KisPart::instance()->createDocument());
90 importDoc->setInfiniteAutoSaveInterval();
91 importDoc->setFileBatchMode(true);
92
93 KisView *setView(0);
94
95 if (!m_batchMode) {
96 // TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater()
97
98// //show the statusbar message even if no view
99// Q_FOREACH (KisView* view, KisPart::instance()->views()) {
100// if (view && view->document() == m_doc) {
101// setView = view;
102// break;
103// }
104// }
105
106// if (!setView) {
107// QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar();
108// if (sb) {
109// sb->showMessage(i18n("Loading CSV file..."));
110// }
111// } else {
112// Q_EMIT m_doc->statusBarMessage(i18n("Loading CSV file..."));
113// }
114
115// Q_EMIT m_doc->sigProgress(0);
116// connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
117 }
118 int step = 0;
119
120 do {
121 qApp->processEvents();
122
123 if (m_stop) {
125 break;
126 }
127
128 if ((idx = readLine.nextLine(io)) <= 0) {
129 if ((idx < 0) ||(step < 5))
131 break;
132 }
133 field = readLine.nextField(); //first field of the line
134
135 if (field.isNull()) continue; //empty row
136
137 switch (step) {
138
139 case 0 : //skip first row
140 step = 1;
141 break;
142
143 case 1 : //scene header names
144 step = 2;
145
146 for (idx = 0; !field.isNull(); idx++) {
147 if (field == "Project Name") {
148 projNameIdx = idx;
149
150 } else if (field == "Width") {
151 widthIdx = idx;
152
153 } else if (field == "Height") {
154 heightIdx = idx;
155
156 } else if (field == "Frame Count") {
157 frameCountIdx = idx;
158
159 } else if (field == "Frame Rate") {
160 framerateIdx = idx;
161
162 } else if (field == "Pixel Aspect Ratio") {
163 pixelRatioIdx = idx;
164 }
165 field= readLine.nextField();
166 }
167 break;
168
169 case 2 : //scene header values
170 step= 3;
171
172 for (idx= 0; !field.isNull(); idx++) {
173 if (idx == projNameIdx) {
174 projName = field;
175
176 } else if (idx == widthIdx) {
177 width = field.toInt();
178
179 } else if (idx == heightIdx) {
180 height = field.toInt();
181
182 } else if (idx == frameCountIdx) {
183 frameCount = field.toInt();
184
185 if (frameCount < 1) frameCount= 1;
186
187 } else if (idx == framerateIdx) {
188 framerate = field.toFloat();
189
190 } else if (idx == pixelRatioIdx) {
191 pixelRatio = field.toFloat();
192
193 }
194 field= readLine.nextField();
195 }
196
197 if ((width < 1) || (height < 1)) {
199 break;
200 }
201
202 retval = createNewImage(width, height, pixelRatio, projName.isNull() ? filename : projName);
203 break;
204
205 case 3 : //create level headers
206 if (field[0] != '#') break;
207
208 for (; !(field = readLine.nextField()).isNull(); ) {
209 CSVLayerRecord* layerRecord = new CSVLayerRecord();
210 layers.append(layerRecord);
211 }
212 readLine.rewind();
213 field = readLine.nextField();
214 step = 4;
215 Q_FALLTHROUGH();
216
217 case 4 : //level header
218
219 if (field == "#Layers") {
220 //layer name
221 for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
222 layers.at(idx)->name = field;
223
224 break;
225 }
226 if (field == "#Density") {
227 //layer opacity
228 for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
229 layers.at(idx)->density = field.toFloat();
230
231 break;
232 }
233 if (field == "#Blending") {
234 //layer blending mode
235 for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
236 layers.at(idx)->blending = field;
237
238 break;
239 }
240 if (field == "#Visible") {
241 //layer visibility
242 for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
243 layers.at(idx)->visible = field.toInt();
244
245 break;
246 }
247 if (field == "#Folder") {
248 //CSV 1.1 folder location
249 for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
250 layers.at(idx)->path = validPath(field, base);
251
252 break;
253 }
254 if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break;
255
256 step = 5;
257
258 Q_FALLTHROUGH();
259
260 case 5 : //frames
261
262 if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break;
263
264 for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) {
265 CSVLayerRecord* layer = layers.at(idx);
266
267 if (layer->last != field) {
268 if (!m_batchMode) {
269 //Q_EMIT m_doc->sigProgress((frame * layers.size() + idx) * 100 /
270 // (frameCount * layers.size()));
271 }
272 retval = setLayer(layer, importDoc.data(), path);
273 layer->last = field;
274 layer->frame = frame;
275 }
276 }
277 frame++;
278 break;
279 }
280 } while (retval.isOk());
281
282 //finish the layers
283
284 if (retval.isOk()) {
285 if (m_image) {
287
288 if (frame > frameCount)
289 frameCount = frame;
290
291 animation->setDocumentRange(KisTimeSpan::fromTimeToTime(0,frameCount - 1));
292 animation->setFramerate((int)framerate);
293 }
294
295 for (idx = 0; idx < layers.size(); idx++) {
296 CSVLayerRecord* layer = layers.at(idx);
297 //empty layers without any pictures are dropped
298
299 if ((layer->frame > 0) || !layer->last.isEmpty()) {
300 retval = setLayer(layer, importDoc.data(), path);
301
302 if (!retval.isOk())
303 break;
304 }
305 }
306 }
307
308 if (m_image) {
309 //insert the existing layers by the right order
310 for (idx = layers.size() - 1; idx >= 0; idx--) {
311 CSVLayerRecord* layer = layers.at(idx);
312
313 if (layer->layer) {
314 m_image->addNode(layer->layer, m_image->root());
315 }
316 }
317 m_image->unlock();
318 }
319 qDeleteAll(layers);
320 io->close();
321
322 if (!m_batchMode) {
323 // disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
324 // Q_EMIT m_doc->sigProgress(100);
325
326 if (!setView) {
327 QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar();
328 if (sb) {
329 sb->clearMessage();
330 }
331 } else {
333 }
334 }
335
336 return retval;
337}
338
339QString CSVLoader::convertBlending(const QString &blending)
340{
341 if (blending == "Color") return COMPOSITE_OVER;
342 if (blending == "Behind") return COMPOSITE_BEHIND;
343 if (blending == "Erase") return COMPOSITE_ERASE;
344 // "Shade"
345 if (blending == "Light") return COMPOSITE_LINEAR_LIGHT;
346 if (blending == "Colorize") return COMPOSITE_COLORIZE;
347 if (blending == "Hue") return COMPOSITE_HUE;
348 if (blending == "Add") return COMPOSITE_ADD;
349 if (blending == "Sub") return COMPOSITE_INVERSE_SUBTRACT;
350 if (blending == "Multiply") return COMPOSITE_MULT;
351 if (blending == "Screen") return COMPOSITE_SCREEN;
352 // "Replace"
353 // "Substitute"
354 if (blending == "Difference") return COMPOSITE_DIFF;
355 if (blending == "Divide") return COMPOSITE_DIVIDE;
356 if (blending == "Overlay") return COMPOSITE_OVERLAY;
357 if (blending == "Light2") return COMPOSITE_DODGE;
358 if (blending == "Shade2") return COMPOSITE_BURN;
359 if (blending == "HardLight") return COMPOSITE_HARD_LIGHT;
360 if (blending == "SoftLight") return COMPOSITE_SOFT_LIGHT_PHOTOSHOP;
361 if (blending == "GrainExtract") return COMPOSITE_GRAIN_EXTRACT;
362 if (blending == "GrainMerge") return COMPOSITE_GRAIN_MERGE;
363 if (blending == "Sub2") return COMPOSITE_SUBTRACT;
364 if (blending == "Darken") return COMPOSITE_DARKEN;
365 if (blending == "Lighten") return COMPOSITE_LIGHTEN;
366 if (blending == "Saturation") return COMPOSITE_SATURATION;
367
368 return COMPOSITE_OVER;
369}
370
371QString CSVLoader::validPath(const QString &path,const QString &base)
372{
373 //replace Windows directory separators with the universal /
374
375 QString tryPath= QString(path).replace(QString("\\"), QString("/"));
376 int i = tryPath.lastIndexOf("/");
377
378 if (i == (tryPath.size() - 1))
379 tryPath= tryPath.left(i); //remove the ending separator if exists
380
381 if (QFileInfo(tryPath).isDir())
382 return tryPath.append("/");
383
384 QString scan(tryPath);
385 i = -1;
386
387 while ((i= (scan.lastIndexOf("/",i) - 1)) > 0) {
388 //avoid testing if the next level will be the default xxxx.layers folder
389
390 if ((i >= 6) && (scan.mid(i - 6, 7) == ".layers")) continue;
391
392 tryPath= QString(base).append(scan.mid(i + 2)); //base already ending with a /
393
394 if (QFileInfo(tryPath).isDir())
395 return tryPath.append("/");
396 }
397 return QString(); //NULL string
398}
399
401{
402 bool result = true;
403
404 if (layer->channel == 0) {
405 //create a new document layer
406
407 float opacity = layer->density;
408
409 if (opacity > 1.0)
410 opacity = 1.0;
411 else if (opacity < 0.0)
412 opacity = 0.0;
413
414 const KoColorSpace* cs = m_image->colorSpace();
415 const QString layerName = (layer->name).isEmpty() ? m_image->nextLayerName() : layer->name;
416
417 KisPaintLayer* paintLayer = new KisPaintLayer(m_image, layerName,
418 (quint8)(opacity * OPACITY_OPAQUE_U8), cs);
419
420 paintLayer->setCompositeOpId(convertBlending(layer->blending));
421 paintLayer->setVisible(layer->visible);
422 paintLayer->enableAnimation();
423
424 layer->layer = paintLayer;
425 layer->channel = qobject_cast<KisRasterKeyframeChannel*>
426 (paintLayer->getKeyframeChannel(KisKeyframeChannel::Raster.id(), true));
427 }
428
429
430 if (!layer->last.isEmpty()) {
431 //png image
432 QString filename = layer->path.isNull() ? path : layer->path;
433 filename.append(layer->last);
434
435 result = importDoc->openPath(filename,
437 if (result)
438 layer->channel->importFrame(layer->frame, importDoc->image()->projection(), nullptr);
439
440 } else {
441 //blank
442 layer->channel->addKeyframe(layer->frame);
443 }
445}
446
447KisImportExportErrorCode CSVLoader::createNewImage(int width, int height, float ratio, const QString &name)
448{
449 //the CSV is RGBA 8bits, sRGB
450
451 if (!m_image) {
454
455 if (cs) m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, name);
456
458
459 m_image->setResolution(ratio, 1.0);
461 }
463}
464
465KisImportExportErrorCode CSVLoader::buildAnimation(QIODevice *io, const QString &filename)
466{
467 return decode(io, filename);
468}
469
471{
472 return m_image;
473}
474
476{
477 m_stop = true;
478}
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
const quint8 OPACITY_OPAQUE_U8
const QString COMPOSITE_OVER
const QString COMPOSITE_DARKEN
const QString COMPOSITE_OVERLAY
const QString COMPOSITE_DIVIDE
const QString COMPOSITE_DODGE
const QString COMPOSITE_ADD
const QString COMPOSITE_LIGHTEN
const QString COMPOSITE_GRAIN_MERGE
const QString COMPOSITE_SOFT_LIGHT_PHOTOSHOP
const QString COMPOSITE_INVERSE_SUBTRACT
const QString COMPOSITE_MULT
const QString COMPOSITE_SATURATION
const QString COMPOSITE_LINEAR_LIGHT
const QString COMPOSITE_COLORIZE
const QString COMPOSITE_HARD_LIGHT
const QString COMPOSITE_SCREEN
const QString COMPOSITE_DIFF
const QString COMPOSITE_ERASE
const QString COMPOSITE_HUE
const QString COMPOSITE_BEHIND
const QString COMPOSITE_SUBTRACT
const QString COMPOSITE_BURN
const QString COMPOSITE_GRAIN_EXTRACT
KisRasterKeyframeChannel * channel
KisImageSP m_image
Definition csv_loader.h:42
~CSVLoader() override
KisImportExportErrorCode buildAnimation(QIODevice *io, const QString &filename)
KisImageSP image()
KisDocument * m_doc
Definition csv_loader.h:43
QString convertBlending(const QString &)
void cancel()
bool m_batchMode
Definition csv_loader.h:44
KisImportExportErrorCode setLayer(CSVLayerRecord *, KisDocument *, const QString &)
QString validPath(const QString &, const QString &)
bool m_stop
Definition csv_loader.h:45
KisImportExportErrorCode decode(QIODevice *io, const QString &filename)
CSVLoader(KisDocument *doc, bool batchMode)
KisImportExportErrorCode createNewImage(int, int, float, const QString &)
QString nextField()
int nextLine(QIODevice *io)
void clearStatusBarMessage()
KisUndoStore * createUndoStore()
KisImageSP image
bool openPath(const QString &path, OpenFlags flags=None)
openPath Open a Path
void setDocumentRange(const KisTimeSpan range)
const KoColorSpace * colorSpace() const
KisImageAnimationInterface * animationInterface() const
void unlock()
Definition kis_image.cc:805
QString nextLayerName(const QString &baseName="") const
Definition kis_image.cc:715
void barrierLock(bool readOnly=false)
Wait until all the queued background jobs are completed and lock the image.
Definition kis_image.cc:756
KisPaintDeviceSP projection() const
void setResolution(double xres, double yres)
static const KoID Raster
void addKeyframe(int time, KUndo2Command *parentUndoCmd=nullptr)
Add a new keyframe to the channel at the specified time.
static KisPart * instance()
Definition KisPart.cpp:131
KisMainWindow * currentMainwindow() const
Definition KisPart.cpp:483
void importFrame(int time, KisPaintDeviceSP sourceDevice, KUndo2Command *parentCommand)
static KisTimeSpan fromTimeToTime(int start, int end)
QString id() const
Definition KoID.cpp:63
#define dbgFile
Definition kis_debug.h:53
KisDocument * createDocument(QList< KisNodeSP > nodes, KisImageSP srcImage, const QRect &copiedBounds)
virtual void setVisible(bool visible, bool loading=false)
KisKeyframeChannel * getKeyframeChannel(const QString &id, bool create)
void setCompositeOpId(const QString &compositeOpId)
void enableAnimation()
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()