1 /*
2  *  Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
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, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "kis_asl_reader.h"
20 
21 #include "kis_dom_utils.h"
22 
23 #include <stdexcept>
24 #include <string>
25 
26 #include <QDomDocument>
27 #include <QIODevice>
28 #include <QBuffer>
29 
30 #include "psd_utils.h"
31 #include "psd.h"
32 #include "compression.h"
33 #include "kis_offset_on_exit_verifier.h"
34 
35 #include "kis_asl_writer_utils.h"
36 #include "kis_asl_reader_utils.h"
37 
38 
39 
40 namespace Private {
41 
42 /**
43  * Numerical fetch functions
44  *
45  * We read numbers and convert them to strings to be able to store
46  * them in XML.
47  */
48 
readDoubleAsString(QIODevice * device)49 QString readDoubleAsString(QIODevice *device) {
50     double value = 0.0;
51     SAFE_READ_EX(device, value);
52 
53     return KisDomUtils::toString(value);
54 }
55 
readIntAsString(QIODevice * device)56 QString readIntAsString(QIODevice *device) {
57     quint32 value = 0.0;
58     SAFE_READ_EX(device, value);
59 
60     return KisDomUtils::toString(value);
61 }
62 
readBoolAsString(QIODevice * device)63 QString readBoolAsString(QIODevice *device) {
64     quint8 value = 0.0;
65     SAFE_READ_EX(device, value);
66 
67     return KisDomUtils::toString(value);
68 }
69 
70 /**
71  * XML generation functions
72  *
73  * Add a node and fill the corresponding attributes
74  */
75 
appendXMLNodeCommon(const QString & key,const QString & value,const QString & type,QDomElement * parent,QDomDocument * doc)76 QDomElement appendXMLNodeCommon(const QString &key, const QString &value, const QString &type, QDomElement *parent, QDomDocument *doc)
77 {
78     QDomElement el = doc->createElement("node");
79     if (!key.isEmpty()) {
80         el.setAttribute("key", key);
81     }
82     el.setAttribute("type", type);
83     el.setAttribute("value", value);
84     parent->appendChild(el);
85 
86     return el;
87 }
88 
appendXMLNodeCommonNoValue(const QString & key,const QString & type,QDomElement * parent,QDomDocument * doc)89 QDomElement appendXMLNodeCommonNoValue(const QString &key, const QString &type, QDomElement *parent, QDomDocument *doc)
90 {
91     QDomElement el = doc->createElement("node");
92     if (!key.isEmpty()) {
93         el.setAttribute("key", key);
94     }
95     el.setAttribute("type", type);
96     parent->appendChild(el);
97 
98     return el;
99 }
100 
appendIntegerXMLNode(const QString & key,const QString & value,QDomElement * parent,QDomDocument * doc)101 void appendIntegerXMLNode(const QString &key, const QString &value, QDomElement *parent, QDomDocument *doc)
102 {
103     appendXMLNodeCommon(key, value, "Integer", parent, doc);
104 }
105 
appendDoubleXMLNode(const QString & key,const QString & value,QDomElement * parent,QDomDocument * doc)106 void appendDoubleXMLNode(const QString &key, const QString &value, QDomElement *parent, QDomDocument *doc)
107 {
108     appendXMLNodeCommon(key, value, "Double", parent, doc);
109 }
110 
appendTextXMLNode(const QString & key,const QString & value,QDomElement * parent,QDomDocument * doc)111 void appendTextXMLNode(const QString &key, const QString &value, QDomElement *parent, QDomDocument *doc)
112 {
113     appendXMLNodeCommon(key, value, "Text", parent, doc);
114 }
115 
appendPointXMLNode(const QString & key,const QPointF & pt,QDomElement * parent,QDomDocument * doc)116 void appendPointXMLNode(const QString &key, const QPointF &pt, QDomElement *parent, QDomDocument *doc)
117 {
118     QDomElement el = appendXMLNodeCommonNoValue(key, "Descriptor", parent, doc);
119     el.setAttribute("classId", "CrPt");
120     el.setAttribute("name", "");
121 
122     appendDoubleXMLNode("Hrzn", KisDomUtils::toString(pt.x()), &el, doc);
123     appendDoubleXMLNode("Vrtc", KisDomUtils::toString(pt.x()), &el, doc);
124 }
125 
126 /**
127  * ASL -> XML parsing functions
128  */
129 
130 void readDescriptor(QIODevice *device,
131                     const QString &key,
132                     QDomElement *parent,
133                     QDomDocument *doc);
134 
readChildObject(QIODevice * device,QDomElement * parent,QDomDocument * doc,bool skipKey=false)135 void readChildObject(QIODevice *device,
136                      QDomElement *parent,
137                      QDomDocument *doc,
138                      bool skipKey = false)
139 {
140     using namespace KisAslReaderUtils;
141 
142     QString key;
143 
144     if (!skipKey) {
145         key = readVarString(device);
146     }
147 
148     QString OSType = readFixedString(device);
149 
150     //dbgKrita << "Child" << ppVar(key) << ppVar(OSType);
151 
152     if (OSType == "obj ") {
153         throw KisAslReaderUtils::ASLParseException("OSType 'obj' not implemented");
154 
155     } else if (OSType == "Objc" || OSType == "GlbO") {
156         readDescriptor(device, key, parent, doc);
157 
158     } else if (OSType == "VlLs") {
159         quint32 numItems = GARBAGE_VALUE_MARK;
160         SAFE_READ_EX(device, numItems);
161 
162         QDomElement el = appendXMLNodeCommonNoValue(key, "List", parent, doc);
163         for (quint32 i = 0; i < numItems; i++) {
164             readChildObject(device, &el, doc, true);
165         }
166 
167     } else if (OSType == "doub") {
168         appendDoubleXMLNode(key, readDoubleAsString(device), parent, doc);
169 
170     } else if (OSType == "UntF") {
171         const QString unit = readFixedString(device);
172         const QString value = readDoubleAsString(device);
173 
174         QDomElement el = appendXMLNodeCommon(key, value, "UnitFloat", parent, doc);
175         el.setAttribute("unit", unit);
176 
177     } else if (OSType == "TEXT") {
178         QString unicodeString = readUnicodeString(device);
179         appendTextXMLNode(key, unicodeString, parent, doc);
180 
181     } else if (OSType == "enum") {
182         const QString typeId = readVarString(device);
183         const QString value = readVarString(device);
184 
185         QDomElement el = appendXMLNodeCommon(key, value, "Enum", parent, doc);
186         el.setAttribute("typeId", typeId);
187 
188     } else if (OSType == "long") {
189         appendIntegerXMLNode(key, readIntAsString(device), parent, doc);
190 
191     } else if (OSType == "bool") {
192         const QString value = readBoolAsString(device);
193         appendXMLNodeCommon(key, value, "Boolean", parent, doc);
194 
195     } else if (OSType == "type") {
196         throw KisAslReaderUtils::ASLParseException("OSType 'type' not implemented");
197     } else if (OSType == "GlbC") {
198         throw KisAslReaderUtils::ASLParseException("OSType 'GlbC' not implemented");
199     } else if (OSType == "alis") {
200         throw KisAslReaderUtils::ASLParseException("OSType 'alis' not implemented");
201     } else if (OSType == "tdta") {
202         throw KisAslReaderUtils::ASLParseException("OSType 'tdta' not implemented");
203     }
204 }
205 
readDescriptor(QIODevice * device,const QString & key,QDomElement * parent,QDomDocument * doc)206 void readDescriptor(QIODevice *device,
207                     const QString &key,
208                     QDomElement *parent,
209                     QDomDocument *doc)
210 {
211     using namespace KisAslReaderUtils;
212 
213     QString name = readUnicodeString(device);
214     QString classId = readVarString(device);
215 
216     quint32 numChildren = GARBAGE_VALUE_MARK;
217     SAFE_READ_EX(device, numChildren);
218 
219     QDomElement el = appendXMLNodeCommonNoValue(key, "Descriptor", parent, doc);
220     el.setAttribute("classId", classId);
221     el.setAttribute("name", name);
222 
223     //dbgKrita << "Descriptor" << ppVar(key) << ppVar(classId) << ppVar(numChildren);
224 
225     for (quint32 i = 0; i < numChildren; i++) {
226         readChildObject(device, &el, doc);
227     }
228 }
229 
readVirtualArrayList(QIODevice * device,int numPlanes)230 QImage readVirtualArrayList(QIODevice *device,
231                             int numPlanes)
232 {
233     using namespace KisAslReaderUtils;
234 
235     quint32 arrayVersion = GARBAGE_VALUE_MARK;
236     SAFE_READ_EX(device, arrayVersion);
237 
238     if (arrayVersion != 3) {
239         throw ASLParseException("VAList version is not '3'!");
240     }
241 
242     quint32 arrayLength = GARBAGE_VALUE_MARK;
243     SAFE_READ_EX(device, arrayLength);
244 
245     SETUP_OFFSET_VERIFIER(vaEndVerifier, device, arrayLength, 100);
246 
247     quint32 x0, y0, x1, y1;
248     SAFE_READ_EX(device, y0);
249     SAFE_READ_EX(device, x0);
250     SAFE_READ_EX(device, y1);
251     SAFE_READ_EX(device, x1);
252     QRect arrayRect(x0, y0, x1 - x0, y1 - y0);
253 
254     quint32 numberOfChannels = GARBAGE_VALUE_MARK;
255     SAFE_READ_EX(device, numberOfChannels);
256 
257     if (numberOfChannels != 24) {
258         throw ASLParseException("VAList: Krita doesn't support ASL files with 'numberOfChannels' flag not equal to 24 (it is not documented)!");
259     }
260 
261     // dbgKrita << ppVar(arrayVersion);
262     // dbgKrita << ppVar(arrayLength);
263     // dbgKrita << ppVar(arrayRect);
264     // dbgKrita << ppVar(numberOfChannels);
265 
266     if (numPlanes != 1 && numPlanes != 3) {
267         throw ASLParseException("VAList: unsupported number of planes!");
268     }
269 
270     QVector<QByteArray> dataPlanes;
271     dataPlanes.resize(3);
272 
273     for (int i = 0; i < numPlanes; i++) {
274         quint32 arrayWritten = GARBAGE_VALUE_MARK;
275         if (!psdread(device, &arrayWritten) || !arrayWritten) {
276             throw ASLParseException("VAList plane has not-written flag set!");
277         }
278 
279         quint32 arrayPlaneLength = GARBAGE_VALUE_MARK;
280         if (!psdread(device, &arrayPlaneLength) || !arrayPlaneLength) {
281             throw ASLParseException("VAList has plane length set to zero!");
282         }
283 
284         SETUP_OFFSET_VERIFIER(planeEndVerifier, device, arrayPlaneLength, 0);
285         qint64 nextPos = device->pos() + arrayPlaneLength;
286 
287         quint32 pixelDepth1 = GARBAGE_VALUE_MARK;
288         SAFE_READ_EX(device, pixelDepth1);
289 
290         quint32 x0, y0, x1, y1;
291         SAFE_READ_EX(device, y0);
292         SAFE_READ_EX(device, x0);
293         SAFE_READ_EX(device, y1);
294         SAFE_READ_EX(device, x1);
295         QRect planeRect(x0, y0, x1 - x0, y1 - y0);
296 
297         if (planeRect != arrayRect) {
298             throw ASLParseException("VAList: planes are not uniform. Not supported yet!");
299         }
300 
301         quint16 pixelDepth2 = GARBAGE_VALUE_MARK;
302         SAFE_READ_EX(device, pixelDepth2);
303 
304         quint8 useCompression = 9;
305         SAFE_READ_EX(device, useCompression);
306 
307         // dbgKrita << "plane index:" << ppVar(i);
308         // dbgKrita << ppVar(arrayWritten);
309         // dbgKrita << ppVar(arrayPlaneLength);
310         // dbgKrita << ppVar(pixelDepth1);
311         // dbgKrita << ppVar(planeRect);
312         // dbgKrita << ppVar(pixelDepth2);
313         // dbgKrita << ppVar(useCompression);
314 
315         if (pixelDepth1 != pixelDepth2) {
316             throw ASLParseException("VAList: two pixel depths of the plane are not equal (it is not documented)!");
317         }
318 
319         if (pixelDepth1 != 8) {
320             throw ASLParseException("VAList: supported pixel depth of the plane in 8 only!");
321         }
322 
323         const int dataLength = planeRect.width() * planeRect.height();
324 
325         if (useCompression == Compression::Uncompressed) {
326             dataPlanes[i] = device->read(dataLength);
327 
328         } else if (useCompression == Compression::RLE) {
329 
330             const int numRows = planeRect.height();
331 
332             QVector<quint16> rowSizes;
333             rowSizes.resize(numRows);
334 
335             for (int row = 0; row < numRows; row++) {
336                 quint16 rowSize = GARBAGE_VALUE_MARK;
337                 SAFE_READ_EX(device, rowSize);
338                 rowSizes[row] = rowSize;
339             }
340 
341             for (int row = 0; row < numRows; row++) {
342                 const quint16 rowSize = rowSizes[row];
343 
344                 QByteArray compressedData = device->read(rowSize);
345 
346                 if (compressedData.size() != rowSize) {
347                     throw ASLParseException("VAList: failed to read compressed data!");
348                 }
349 
350                 QByteArray uncompressedData = Compression::uncompress(planeRect.width(), compressedData, Compression::RLE);
351 
352                 if (uncompressedData.size() != planeRect.width()) {
353                     throw ASLParseException("VAList: failed to decompress data!");
354                 }
355 
356                 dataPlanes[i].append(uncompressedData);
357             }
358         } else {
359             throw ASLParseException("VAList: ZIP compression is not implemented yet!");
360         }
361 
362         if (dataPlanes[i].size() != dataLength) {
363             throw ASLParseException("VAList: failed to read/uncompress data plane!");
364         }
365 
366         device->seek(nextPos);
367     }
368 
369     QImage image(arrayRect.size(), QImage::Format_ARGB32);
370 
371     const int dataLength = arrayRect.width() * arrayRect.height();
372     quint8 *dstPtr = image.bits();
373 
374     for (int i = 0; i < dataLength; i++) {
375         for (int j = 2; j >= 0; j--) {
376             int plane = qMin(numPlanes, j);
377             *dstPtr++ = dataPlanes[plane][i];
378         }
379         *dstPtr++ = 0xFF;
380     }
381 
382 #if 0
383      static int i = -1; i++;
384      QString filename = QString("pattern_image_%1.png").arg(i);
385      dbgKrita << "### dumping pattern image" << ppVar(filename);
386      image.save(filename);
387 #endif
388 
389     return image;
390 }
391 
readPattern(QIODevice * device,QDomElement * parent,QDomDocument * doc)392 qint64 readPattern(QIODevice *device,
393                    QDomElement *parent,
394                    QDomDocument *doc)
395 {
396     using namespace KisAslReaderUtils;
397 
398     quint32 patternSize = GARBAGE_VALUE_MARK;
399     SAFE_READ_EX(device, patternSize);
400 
401     // patterns are always aligned by 4 bytes
402     patternSize = KisAslWriterUtils::alignOffsetCeil(patternSize, 4);
403 
404     SETUP_OFFSET_VERIFIER(patternEndVerifier, device, patternSize, 0);
405 
406     quint32 patternVersion = GARBAGE_VALUE_MARK;
407     SAFE_READ_EX(device, patternVersion);
408 
409     if (patternVersion != 1) {
410         throw ASLParseException("Pattern version is not \'1\'");
411     }
412 
413     quint32 patternImageMode = GARBAGE_VALUE_MARK;
414     SAFE_READ_EX(device, patternImageMode);
415 
416     quint16 patternHeight = GARBAGE_VALUE_MARK;
417     SAFE_READ_EX(device, patternHeight);
418 
419     quint16 patternWidth = GARBAGE_VALUE_MARK;
420     SAFE_READ_EX(device, patternWidth);
421 
422     QString patternName;
423     psdread_unicodestring(device, patternName);
424 
425     QString patternUuid = readPascalString(device);
426 
427     // dbgKrita << "--";
428     // dbgKrita << ppVar(patternSize);
429     // dbgKrita << ppVar(patternImageMode);
430     // dbgKrita << ppVar(patternHeight);
431     // dbgKrita << ppVar(patternWidth);
432     // dbgKrita << ppVar(patternName);
433     // dbgKrita << ppVar(patternUuid);
434 
435 
436     int numPlanes = 0;
437     psd_color_mode mode = static_cast<psd_color_mode>(patternImageMode);
438 
439     switch (mode) {
440     case MultiChannel:
441     case Grayscale:
442         numPlanes = 1;
443         break;
444     case RGB:
445         numPlanes = 3;
446         break;
447     default: {
448         QString msg = QString("Unsupported image mode: %1!").arg(mode);
449         throw ASLParseException(msg);
450     }
451     }
452 
453     /**
454      * Create XML data
455      */
456 
457     QDomElement pat = doc->createElement("node");
458 
459     pat.setAttribute("classId", "KisPattern");
460     pat.setAttribute("type", "Descriptor");
461     pat.setAttribute("name", "");
462 
463     QBuffer patternBuf;
464     patternBuf.open(QIODevice::WriteOnly);
465 
466     { // ensure we don't keep resources for too long
467         QString fileName = QString("%1.pat").arg(patternUuid);
468         QImage patternImage = readVirtualArrayList(device, numPlanes);
469         KoPattern realPattern(patternImage, patternName, fileName);
470         realPattern.savePatToDevice(&patternBuf);
471     }
472 
473     /**
474      * We are loading the pattern and convert it into ARGB right away,
475      * so we need not store real image mode and size of the pattern
476      * externally.
477      */
478     appendTextXMLNode("Nm  ", patternName, &pat, doc);
479     appendTextXMLNode("Idnt", patternUuid, &pat, doc);
480 
481     QDomCDATASection dataSection = doc->createCDATASection(qCompress(patternBuf.buffer()).toBase64());
482 
483     QDomElement dataElement = doc->createElement("node");
484     dataElement.setAttribute("type", "KisPatternData");
485     dataElement.setAttribute("key", "Data");
486 
487     dataElement.appendChild(dataSection);
488     pat.appendChild(dataElement);
489     parent->appendChild(pat);
490 
491     return sizeof(patternSize) + patternSize;
492 }
493 
readFileImpl(QIODevice * device)494 QDomDocument readFileImpl(QIODevice *device)
495 {
496     using namespace KisAslReaderUtils;
497 
498     QDomDocument doc;
499     QDomElement root = doc.createElement("asl");
500     doc.appendChild(root);
501 
502 
503     {
504         quint16 stylesVersion = GARBAGE_VALUE_MARK;
505         SAFE_READ_SIGNATURE_EX(device, stylesVersion, 2);
506     }
507 
508     {
509         quint32 aslSignature = GARBAGE_VALUE_MARK;
510         const quint32 refSignature = 0x3842534c; // '8BSL' in little-endian
511         SAFE_READ_SIGNATURE_EX(device, aslSignature, refSignature);
512     }
513 
514     {
515         quint16 patternsVersion = GARBAGE_VALUE_MARK;
516         SAFE_READ_SIGNATURE_EX(device, patternsVersion, 3);
517     }
518 
519     // Patterns
520 
521     {
522         quint32 patternsSize = GARBAGE_VALUE_MARK;
523         SAFE_READ_EX(device, patternsSize);
524 
525         if (patternsSize > 0) {
526 
527             SETUP_OFFSET_VERIFIER(patternsSectionVerifier, device, patternsSize, 0);
528 
529             QDomElement patternsRoot = doc.createElement("node");
530             patternsRoot.setAttribute("type", "List");
531             patternsRoot.setAttribute("key", "Patterns");
532             root.appendChild(patternsRoot);
533 
534             try {
535                 qint64 bytesRead = 0;
536                 while (bytesRead < patternsSize) {
537                     qint64 chunk = readPattern(device, &patternsRoot, &doc);
538                     bytesRead += chunk;
539                 }
540             } catch (ASLParseException &e) {
541                 warnKrita << "WARNING: ASL (emb. pattern):" << e.what();
542             }
543         }
544     }
545 
546     // Styles
547 
548     quint32 numStyles = GARBAGE_VALUE_MARK;
549     SAFE_READ_EX(device, numStyles);
550 
551     for (int i = 0; i < (int)numStyles; i++) {
552 
553         quint32 bytesToRead = GARBAGE_VALUE_MARK;
554         SAFE_READ_EX(device, bytesToRead);
555 
556         SETUP_OFFSET_VERIFIER(singleStyleSectionVerifier, device, bytesToRead, 0);
557 
558         {
559             quint32 stylesFormatVersion = GARBAGE_VALUE_MARK;
560             SAFE_READ_SIGNATURE_EX(device, stylesFormatVersion, 16);
561         }
562 
563         readDescriptor(device, "", &root, &doc);
564 
565         {
566             quint32 stylesFormatVersion = GARBAGE_VALUE_MARK;
567             SAFE_READ_SIGNATURE_EX(device, stylesFormatVersion, 16);
568         }
569 
570         readDescriptor(device, "", &root, &doc);
571     }
572 
573     return doc;
574 }
575 
576 } // namespace
577 
readFile(QIODevice * device)578 QDomDocument KisAslReader::readFile(QIODevice *device)
579 {
580     QDomDocument doc;
581 
582     if (device->isSequential()) {
583         warnKrita << "WARNING: *** KisAslReader::readFile: the supplied"
584                    << "IO device is sequential. Chances are that"
585                    << "the layer style will *not* be loaded correctly!";
586     }
587 
588     try {
589         doc = Private::readFileImpl(device);
590     } catch (KisAslReaderUtils::ASLParseException &e) {
591         warnKrita << "WARNING: ASL:" << e.what();
592     }
593 
594     return doc;
595 }
596 
readLfx2PsdSection(QIODevice * device)597 QDomDocument KisAslReader::readLfx2PsdSection(QIODevice *device)
598 {
599     QDomDocument doc;
600 
601     if (device->isSequential()) {
602         warnKrita << "WARNING: *** KisAslReader::readLfx2PsdSection: the supplied"
603                    << "IO device is sequential. Chances are that"
604                    << "the layer style will *not* be loaded correctly!";
605     }
606 
607     try {
608         {
609             quint32 objectEffectsVersion = GARBAGE_VALUE_MARK;
610             const quint32 ref = 0x00;
611             SAFE_READ_SIGNATURE_EX(device, objectEffectsVersion, ref);
612         }
613 
614         {
615             quint32 descriptorVersion = GARBAGE_VALUE_MARK;
616             const quint32 ref = 0x10;
617             SAFE_READ_SIGNATURE_EX(device, descriptorVersion, ref);
618         }
619 
620 
621         QDomElement root = doc.createElement("asl");
622         doc.appendChild(root);
623 
624         Private::readDescriptor(device, "", &root, &doc);
625 
626     } catch (KisAslReaderUtils::ASLParseException &e) {
627         warnKrita << "WARNING: PSD: lfx2 section:" << e.what();
628     }
629 
630     return doc;
631 }
632 
readPsdSectionPattern(QIODevice * device,qint64 bytesLeft)633 QDomDocument KisAslReader::readPsdSectionPattern(QIODevice *device, qint64 bytesLeft)
634 {
635     QDomDocument doc;
636 
637     QDomElement root = doc.createElement("asl");
638     doc.appendChild(root);
639 
640     QDomElement pat = doc.createElement("node");
641     root.appendChild(pat);
642 
643     pat.setAttribute("classId", "Patterns");
644     pat.setAttribute("type", "Descriptor");
645     pat.setAttribute("name", "");
646 
647     try {
648         qint64 bytesRead = 0;
649         while (bytesRead < bytesLeft) {
650             qint64 chunk = Private::readPattern(device, &pat, &doc);
651             bytesRead += chunk;
652         }
653     } catch (KisAslReaderUtils::ASLParseException &e) {
654         warnKrita << "WARNING: PSD (emb. pattern):" << e.what();
655     }
656 
657     return doc;
658 }
659