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