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