1 /*
2  *  Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #include "csv_saver.h"
21 
22 #include <QDebug>
23 #include <QApplication>
24 
25 #include <QFileInfo>
26 #include <QFile>
27 #include <QDir>
28 #include <QVector>
29 #include <QIODevice>
30 #include <QRect>
31 #include <KisMimeDatabase.h>
32 
33 #include <KisPart.h>
34 #include <KisDocument.h>
35 #include <KoColorSpace.h>
36 #include <KoColorSpaceRegistry.h>
37 #include <KoColorModelStandardIds.h>
38 
39 #include <kis_annotation.h>
40 #include <kis_types.h>
41 
42 #include <kis_debug.h>
43 #include <kis_image.h>
44 #include <kis_group_layer.h>
45 #include <kis_paint_layer.h>
46 #include <kis_paint_device.h>
47 #include <kis_raster_keyframe_channel.h>
48 #include <kis_image_animation_interface.h>
49 #include <kis_time_range.h>
50 #include <kis_iterator_ng.h>
51 
52 #include "csv_layer_record.h"
53 
CSVSaver(KisDocument * doc,bool batchMode)54 CSVSaver::CSVSaver(KisDocument *doc, bool batchMode)
55     : m_image(doc->savingImage())
56     , m_doc(doc)
57     , m_batchMode(batchMode)
58     , m_stop(false)
59 {
60 }
61 
~CSVSaver()62 CSVSaver::~CSVSaver()
63 {
64 }
65 
image()66 KisImageSP CSVSaver::image()
67 {
68     return m_image;
69 }
70 
encode(QIODevice * io)71 KisImportExportErrorCode CSVSaver::encode(QIODevice *io)
72 {
73     int idx;
74     int start, end;
75     KisNodeSP node;
76     QByteArray ba;
77     KisKeyframeSP keyframe;
78     QVector<CSVLayerRecord*> layers;
79 
80     KisImageAnimationInterface *animation = m_image->animationInterface();
81 
82     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
83 
84 // XXX: Stream was unused?
85 //    //DataStream instead of TextStream for correct line endings
86 //    QDataStream stream(&f);
87 
88     //Using the original local path
89     QString path = m_doc->localFilePath();
90 
91     if (path.right(4).toUpper() == ".CSV")
92         path = path.left(path.size() - 4);
93     else {
94         // something is wrong: the local file name is not .csv!
95         // trying the given (probably temporary) filename as well
96 
97         KIS_SAFE_ASSERT_RECOVER(0 && "Wrong extension of the saved file!") {
98             path = path.left(path.size() - 4);
99         }
100     }
101     path.append(".frames");
102 
103     //create directory
104 
105     QDir dir(path);
106     if (!dir.exists()) {
107         dir.mkpath(".");
108     }
109     //according to the QT docs, the slash is a universal directory separator
110     path.append("/");
111 
112     node = m_image->rootLayer()->firstChild();
113 
114     //TODO: correct handling of the layer tree.
115     //for now, only top level paint layers are saved
116 
117     idx = 0;
118 
119     while (node) {
120         if (node->inherits("KisLayer")) {
121             KisLayer* paintLayer = qobject_cast<KisLayer*>(node.data());
122             CSVLayerRecord* layerRecord = new CSVLayerRecord();
123             layers.prepend(layerRecord); //reverse order!
124 
125             layerRecord->name = paintLayer->name();
126             layerRecord->name.replace(QRegExp("[\"\\r\\n]"), "_");
127 
128             if (layerRecord->name.isEmpty())
129                 layerRecord->name= QString("Unnamed-%1").arg(idx);
130 
131             layerRecord->visible = (paintLayer->visible()) ? 1 : 0;
132             layerRecord->density = (float)(paintLayer->opacity()) / OPACITY_OPAQUE_U8;
133             layerRecord->blending = convertToBlending(paintLayer->compositeOpId());
134             layerRecord->layer = paintLayer;
135             layerRecord->channel = paintLayer->original()->keyframeChannel();
136             layerRecord->last = "";
137             layerRecord->frame = 0;
138             idx++;
139         }
140         node = node->nextSibling();
141     }
142 
143     KisTimeRange range = animation->fullClipRange();
144 
145     start = (range.isValid()) ? range.start() : 0;
146 
147     if (!range.isInfinite()) {
148         end = range.end();
149 
150         if (end < start) end = start;
151     } else {
152         //undefined length, searching for the last keyframe
153         end = start;
154 
155         for (idx = 0; idx < layers.size(); idx++) {
156             KisRasterKeyframeChannel *channel = layers.at(idx)->channel;
157 
158             if (channel) {
159                 keyframe = channel->lastKeyframe();
160 
161                 if ( (!keyframe.isNull()) && (keyframe->time() > end) )
162                     end = keyframe->time();
163             }
164         }
165     }
166 
167     //create temporary doc for exporting
168     QScopedPointer<KisDocument> exportDoc(KisPart::instance()->createDocument());
169     createTempImage(exportDoc.data());
170 
171     KisImportExportErrorCode retval= ImportExportCodes::OK;
172 
173     if (!m_batchMode) {
174         // TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater()
175         //emit m_doc->statusBarMessage(i18n("Saving CSV file..."));
176         //emit m_doc->sigProgress(0);
177         //connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
178     }
179     int frame = start;
180     int step = 0;
181 
182     do {
183         qApp->processEvents();
184 
185         if (m_stop) {
186             retval = ImportExportCodes::Cancelled;
187             break;
188         }
189 
190         switch(step) {
191 
192         case 0 :    //first row
193             if (io->write("UTF-8, TVPaint, \"CSV 1.0\"\r\n") < 0) {
194                 retval = ImportExportCodes::Failure;
195             }
196             break;
197 
198         case 1 :    //scene header names
199             if (io->write("Project Name, Width, Height, Frame Count, Layer Count, Frame Rate, Pixel Aspect Ratio, Field Mode\r\n") < 0) {
200                 retval = ImportExportCodes::Failure;
201             }
202             break;
203 
204         case 2 :    //scene header values
205             ba = QString("\"%1\", ").arg(m_image->objectName()).toUtf8();
206             if (io->write(ba.data()) < 0) {
207                 retval = ImportExportCodes::Failure;
208                 break;
209             }
210             ba = QString("%1, %2, ").arg(m_image->width()).arg(m_image->height()).toUtf8();
211             if (io->write(ba.data()) < 0) {
212                 retval = ImportExportCodes::Failure;
213                 break;
214             }
215 
216             ba = QString("%1, %2, ").arg(end - start + 1).arg(layers.size()).toUtf8();
217             if (io->write(ba.data()) < 0) {
218                 retval = ImportExportCodes::Failure;
219                 break;
220             }
221             //the framerate is an integer here
222             ba = QString("%1, ").arg((double)(animation->framerate()),0,'f',6).toUtf8();
223             if (io->write(ba.data()) < 0) {
224                 retval = ImportExportCodes::Failure;
225                 break;
226             }
227             ba = QString("%1, Progressive\r\n").arg((double)(m_image->xRes() / m_image->yRes()),0,'f',6).toUtf8();
228             if (io->write(ba.data()) < 0) {
229                 retval = ImportExportCodes::Failure;
230                 break;
231             }
232             break;
233 
234         case 3 :    //layer header values
235             if (io->write("#Layers") < 0) {          //Layers
236                 retval = ImportExportCodes::Failure;
237                 break;
238             }
239 
240             for (idx = 0; idx < layers.size(); idx++) {
241                 ba = QString(", \"%1\"").arg(layers.at(idx)->name).toUtf8();
242                 if (io->write(ba.data()) < 0)
243                     break;
244             }
245             break;
246 
247         case 4 :
248             if (io->write("\r\n#Density") < 0) {     //Density
249                 retval = ImportExportCodes::Failure;
250                 break;
251             }
252             for (idx = 0; idx < layers.size(); idx++) {
253                 ba = QString(", %1").arg((double)(layers.at(idx)->density), 0, 'f', 6).toUtf8();
254                 if (io->write(ba.data()) < 0)
255                     break;
256             }
257             break;
258 
259         case 5 :
260             if (io->write("\r\n#Blending") < 0) {     //Blending
261                 retval = ImportExportCodes::Failure;
262                 break;
263             }
264             for (idx = 0; idx < layers.size(); idx++) {
265                 ba = QString(", \"%1\"").arg(layers.at(idx)->blending).toUtf8();
266                 if (io->write(ba.data()) < 0)
267                     break;
268             }
269             break;
270 
271         case 6 :
272             if (io->write("\r\n#Visible") < 0) {     //Visible
273                 retval = ImportExportCodes::Failure;
274                 break;
275             }
276             for (idx = 0; idx < layers.size(); idx++) {
277                 ba = QString(", %1").arg(layers.at(idx)->visible).toUtf8();
278                 if (io->write(ba.data()) < 0)
279                     break;
280             }
281             if (idx < layers.size()) {
282                 retval = ImportExportCodes::Failure;
283             }
284             break;
285 
286         default :    //frames
287 
288             if (frame > end) {
289                 if (io->write("\r\n") < 0)
290                     retval = ImportExportCodes::Failure;
291 
292                 step = 8;
293                 break;
294             }
295 
296             ba = QString("\r\n#%1").arg(frame, 5, 10, QChar('0')).toUtf8();
297             if (io->write(ba.data()) < 0) {
298                 retval = ImportExportCodes::Failure;
299                 break;
300             }
301 
302             for (idx = 0; idx < layers.size(); idx++) {
303                 CSVLayerRecord *layer = layers.at(idx);
304                 KisRasterKeyframeChannel *channel = layer->channel;
305 
306                 if (channel) {
307                     if (frame == start) {
308                         keyframe = channel->activeKeyframeAt(frame);
309                     } else {
310                         keyframe = channel->keyframeAt(frame);
311                     }
312                 } else {
313                     keyframe.clear(); // without animation
314                 }
315 
316                 if ( !keyframe.isNull() || (frame == start) ) {
317 
318                     if (!m_batchMode) {
319                         //emit m_doc->sigProgress(((frame - start) * layers.size() + idx) * 100 /
320                         //                        ((end - start) * layers.size()));
321                     }
322                     retval = getLayer(layer, exportDoc.data(), keyframe, path, frame, idx);
323 
324                     if (!retval.isOk())
325                         break;
326                 }
327                 ba = QString(", \"%1\"").arg(layer->last).toUtf8();
328 
329                 if (io->write(ba.data()) < 0)
330                     break;
331             }
332             if (idx < layers.size())
333                 retval = ImportExportCodes::Failure;
334 
335             frame++;
336             step = 6; //keep step here
337             break;
338         }
339         step++;
340     } while((retval.isOk()) && (step < 8));
341 
342     qDeleteAll(layers);
343 
344     // io->close();  it seems this is not required anymore
345 
346     if (!m_batchMode) {
347         //disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
348         //emit m_doc->sigProgress(100);
349         //emit m_doc->clearStatusBarMessage();
350     }
351     QApplication::restoreOverrideCursor();
352     return retval;
353 }
354 
convertToBlending(const QString & opid)355 QString CSVSaver::convertToBlending(const QString &opid)
356 {
357     if (opid == COMPOSITE_OVER) return "Color";
358     if (opid == COMPOSITE_BEHIND) return "Behind";
359     if (opid == COMPOSITE_ERASE) return "Erase";
360     // "Shade"
361     if (opid == COMPOSITE_LINEAR_LIGHT) return "Light";
362     if (opid == COMPOSITE_COLORIZE) return "Colorize";
363     if (opid == COMPOSITE_HUE) return "Hue";
364     if ((opid == COMPOSITE_ADD) ||
365         (opid == COMPOSITE_LINEAR_DODGE)) return "Add";
366     if (opid == COMPOSITE_INVERSE_SUBTRACT) return "Sub";
367     if (opid == COMPOSITE_MULT) return "Multiply";
368     if (opid == COMPOSITE_SCREEN) return "Screen";
369     // "Replace"
370     // "Substitute"
371     if (opid == COMPOSITE_DIFF) return "Difference";
372     if (opid == COMPOSITE_DIVIDE) return "Divide";
373     if (opid == COMPOSITE_OVERLAY) return "Overlay";
374     if (opid == COMPOSITE_DODGE) return "Light2";
375     if (opid == COMPOSITE_BURN) return "Shade2";
376     if (opid == COMPOSITE_HARD_LIGHT) return "HardLight";
377     if ((opid == COMPOSITE_SOFT_LIGHT_PHOTOSHOP) ||
378         (opid == COMPOSITE_SOFT_LIGHT_SVG)) return "SoftLight";
379     if (opid == COMPOSITE_GRAIN_EXTRACT) return "GrainExtract";
380     if (opid == COMPOSITE_GRAIN_MERGE) return "GrainMerge";
381     if (opid == COMPOSITE_SUBTRACT) return "Sub2";
382     if (opid == COMPOSITE_DARKEN) return "Darken";
383     if (opid == COMPOSITE_LIGHTEN) return "Lighten";
384     if (opid == COMPOSITE_SATURATION) return "Saturation";
385 
386     return "Color";
387 }
388 
getLayer(CSVLayerRecord * layer,KisDocument * exportDoc,KisKeyframeSP keyframe,const QString & path,int frame,int idx)389 KisImportExportErrorCode CSVSaver::getLayer(CSVLayerRecord* layer, KisDocument* exportDoc, KisKeyframeSP keyframe, const QString &path, int frame, int idx)
390 {
391     //render to the temp layer
392     KisImageSP image = exportDoc->savingImage();
393 
394     if (!image) image= exportDoc->image();
395 
396     KisPaintDeviceSP device = image->rootLayer()->firstChild()->projection();
397 
398     if (!keyframe.isNull()) {
399         layer->channel->fetchFrame(keyframe, device);
400     } else {
401         device->makeCloneFrom(layer->layer->projection(),image->bounds()); // without animation
402     }
403     QRect bounds = device->exactBounds();
404 
405     if (bounds.isEmpty()) {
406         layer->last = "";                   //empty frame
407         return ImportExportCodes::OK;
408     }
409     layer->last = QString("frame%1-%2.png").arg(idx + 1,5,10,QChar('0')).arg(frame,5,10,QChar('0'));
410 
411     QString filename = path;
412     filename.append(layer->last);
413 
414     //save to PNG
415     KisSequentialConstIterator it(device, image->bounds());
416     const KoColorSpace* cs = device->colorSpace();
417 
418     bool isThereAlpha = false;
419     while (it.nextPixel()) {
420         if (cs->opacityU8(it.oldRawData()) != OPACITY_OPAQUE_U8) {
421             isThereAlpha = true;
422             break;
423         }
424     }
425 
426     if (!KisPNGConverter::isColorSpaceSupported(cs)) {
427         device = new KisPaintDevice(*device.data());
428         device->convertTo(KoColorSpaceRegistry::instance()->rgb8());
429     }
430     KisPNGOptions options;
431 
432     options.alpha = isThereAlpha;
433     options.interlace = false;
434     options.compression = 8;
435     options.tryToSaveAsIndexed = false;
436     options.transparencyFillColor = QColor(0,0,0);
437     options.saveSRGBProfile = true;                 //TVPaint can use only sRGB
438     options.forceSRGB = false;
439 
440     KisPNGConverter kpc(exportDoc);
441 
442     KisImportExportErrorCode result = kpc.buildFile(filename, image->bounds(),
443                                                   image->xRes(), image->yRes(), device,
444                                                   image->beginAnnotations(), image->endAnnotations(),
445                                                   options, (KisMetaData::Store* )0 );
446 
447     return result;
448 }
449 
createTempImage(KisDocument * exportDoc)450 void CSVSaver::createTempImage(KisDocument* exportDoc)
451 {
452     exportDoc->setInfiniteAutoSaveInterval();
453     exportDoc->setFileBatchMode(true);
454 
455     KisImageSP exportImage = new KisImage(exportDoc->createUndoStore(),
456                                            m_image->width(), m_image->height(), m_image->colorSpace(),
457                                            QString());
458 
459     exportImage->setResolution(m_image->xRes(), m_image->yRes());
460     exportDoc->setCurrentImage(exportImage);
461 
462     KisPaintLayer* paintLayer = new KisPaintLayer(exportImage, "paint device", OPACITY_OPAQUE_U8);
463     exportImage->addNode(paintLayer, exportImage->rootLayer(), KisLayerSP(0));
464 }
465 
466 
buildAnimation(QIODevice * io)467 KisImportExportErrorCode CSVSaver::buildAnimation(QIODevice *io)
468 {
469     KIS_ASSERT_RECOVER_RETURN_VALUE(m_image, ImportExportCodes::InternalError);
470     return encode(io);
471 }
472 
cancel()473 void CSVSaver::cancel()
474 {
475     m_stop = true;
476 }
477