1 | /* |
2 | Photoshop File Format support for QImage. |
3 | |
4 | SPDX-FileCopyrightText: 2003 Ignacio CastaƱo <castano@ludicon.com> |
5 | SPDX-FileCopyrightText: 2015 Alex Merry <alex.merry@kde.org> |
6 | SPDX-FileCopyrightText: 2022-2023 Mirco Miranda <mircomir@outlook.com> |
7 | |
8 | SPDX-License-Identifier: LGPL-2.0-or-later |
9 | */ |
10 | |
11 | /* |
12 | * The early version of this code was based on Thacher Ulrich PSD loading code |
13 | * released into the public domain. See: http://tulrich.com/geekstuff/ |
14 | */ |
15 | |
16 | /* |
17 | * Documentation on this file format is available at |
18 | * http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/ |
19 | */ |
20 | |
21 | /* |
22 | * Limitations of the current code: |
23 | * - Color spaces other than RGB/Grayscale cannot be read due to lack of QImage |
24 | * support. Where possible, a conversion to RGB is done: |
25 | * - CMYK images are converted using an approximated way that ignores the color |
26 | * information (ICC profile). |
27 | * - LAB images are converted to sRGB using literature formulas. |
28 | * - MULICHANNEL images more than 3 channels are converted as CMYK images. |
29 | * - DUOTONE images are considered as Grayscale images. |
30 | * |
31 | * NOTE: The best way to convert between different color spaces is to use a |
32 | * color management engine (e.g. LittleCMS). |
33 | */ |
34 | |
35 | #include "fastmath_p.h" |
36 | #include "psd_p.h" |
37 | #include "util_p.h" |
38 | |
39 | #include <QDataStream> |
40 | #include <QDebug> |
41 | #include <QImage> |
42 | #include <QColorSpace> |
43 | |
44 | #include <cmath> |
45 | #include <cstring> |
46 | |
47 | typedef quint32 uint; |
48 | typedef quint16 ushort; |
49 | typedef quint8 uchar; |
50 | |
51 | /* The fast LAB conversion converts the image to linear sRgb instead to sRgb. |
52 | * This should not be a problem because the Qt's QColorSpace supports the linear |
53 | * sRgb colorspace. |
54 | * |
55 | * Using linear conversion, the loading speed is slightly improved. Anyway, if you are using |
56 | * an software that discard color info, you should comment it. |
57 | * |
58 | * At the time I'm writing (07/2022), Gwenview and Krita supports linear sRgb but KDE |
59 | * preview creator does not. This is the why, for now, it is disabled. |
60 | */ |
61 | //#define PSD_FAST_LAB_CONVERSION |
62 | |
63 | namespace // Private. |
64 | { |
65 | |
66 | enum Signature : quint32 { |
67 | S_8BIM = 0x3842494D, // '8BIM' |
68 | S_8B64 = 0x38423634, // '8B64' |
69 | |
70 | S_MeSa = 0x4D655361 // 'MeSa' |
71 | }; |
72 | |
73 | enum ColorMode : quint16 { |
74 | CM_BITMAP = 0, |
75 | CM_GRAYSCALE = 1, |
76 | CM_INDEXED = 2, |
77 | CM_RGB = 3, |
78 | CM_CMYK = 4, |
79 | CM_MULTICHANNEL = 7, |
80 | CM_DUOTONE = 8, |
81 | CM_LABCOLOR = 9, |
82 | }; |
83 | |
84 | enum ImageResourceId : quint16 { |
85 | IRI_RESOLUTIONINFO = 0x03ED, |
86 | IRI_ICCPROFILE = 0x040F, |
87 | IRI_TRANSPARENCYINDEX = 0x0417, |
88 | IRI_VERSIONINFO = 0x0421, |
89 | IRI_XMPMETADATA = 0x0424 |
90 | }; |
91 | |
92 | enum LayerId : quint32 { |
93 | LI_MT16 = 0x4D743136, // 'Mt16', |
94 | LI_MT32 = 0x4D743332, // 'Mt32', |
95 | LI_MTRN = 0x4D74726E // 'Mtrn' |
96 | }; |
97 | |
98 | struct { |
99 | uint ; |
100 | ushort ; |
101 | uchar [6]; |
102 | ushort ; |
103 | uint ; |
104 | uint ; |
105 | ushort ; |
106 | ushort ; |
107 | }; |
108 | |
109 | struct PSDImageResourceBlock { |
110 | QString name; |
111 | QByteArray data; |
112 | }; |
113 | |
114 | /*! |
115 | * \brief The PSDDuotoneOptions struct |
116 | * \note You can decode the duotone data using the "Duotone Options" |
117 | * file format found in the "Photoshop File Format" specs. |
118 | */ |
119 | struct PSDDuotoneOptions { |
120 | QByteArray data; |
121 | }; |
122 | |
123 | /*! |
124 | * \brief The PSDColorModeDataSection struct |
125 | * Only indexed color and duotone have color mode data. |
126 | */ |
127 | struct PSDColorModeDataSection { |
128 | PSDDuotoneOptions duotone; |
129 | QList<QRgb> palette; |
130 | }; |
131 | |
132 | using PSDImageResourceSection = QHash<quint16, PSDImageResourceBlock>; |
133 | |
134 | struct PSDLayerInfo { |
135 | qint64 size = -1; |
136 | qint16 layerCount = 0; |
137 | }; |
138 | |
139 | struct PSDGlobalLayerMaskInfo { |
140 | qint64 size = -1; |
141 | }; |
142 | |
143 | struct PSDAdditionalLayerInfo { |
144 | Signature signature = Signature(); |
145 | LayerId id = LayerId(); |
146 | qint64 size = -1; |
147 | }; |
148 | |
149 | struct PSDLayerAndMaskSection { |
150 | qint64 size = -1; |
151 | PSDLayerInfo layerInfo; |
152 | PSDGlobalLayerMaskInfo globalLayerMaskInfo; |
153 | QHash<LayerId, PSDAdditionalLayerInfo> additionalLayerInfo; |
154 | |
155 | bool isNull() const { |
156 | return (size <= 0); |
157 | } |
158 | |
159 | bool hasAlpha() const { |
160 | return layerInfo.layerCount < 0 || |
161 | additionalLayerInfo.contains(key: LI_MT16) || |
162 | additionalLayerInfo.contains(key: LI_MT32) || |
163 | additionalLayerInfo.contains(key: LI_MTRN); |
164 | } |
165 | |
166 | bool atEnd(bool isPsb) const { |
167 | qint64 currentSize = 0; |
168 | if (layerInfo.size > -1) { |
169 | currentSize += layerInfo.size + 4; |
170 | if (isPsb) |
171 | currentSize += 4; |
172 | } |
173 | if (globalLayerMaskInfo.size > -1) { |
174 | currentSize += globalLayerMaskInfo.size + 4; |
175 | } |
176 | auto aliv = additionalLayerInfo.values(); |
177 | for (auto &&v : aliv) { |
178 | currentSize += (12 + v.size); |
179 | if (v.signature == S_8B64) |
180 | currentSize += 4; |
181 | } |
182 | return (size <= currentSize); |
183 | } |
184 | }; |
185 | |
186 | /*! |
187 | * \brief fixedPointToDouble |
188 | * Converts a fixed point number to floating point one. |
189 | */ |
190 | static double fixedPointToDouble(qint32 fixedPoint) |
191 | { |
192 | auto i = double(fixedPoint >> 16); |
193 | auto d = double((fixedPoint & 0x0000FFFF) / 65536.0); |
194 | return (i+d); |
195 | } |
196 | |
197 | static qint64 readSize(QDataStream &s, bool psb = false) |
198 | { |
199 | qint64 size = 0; |
200 | if (!psb) { |
201 | quint32 tmp; |
202 | s >> tmp; |
203 | size = tmp; |
204 | } |
205 | else { |
206 | s >> size; |
207 | } |
208 | if (s.status() != QDataStream::Ok) { |
209 | size = -1; |
210 | } |
211 | return size; |
212 | } |
213 | |
214 | static bool skip_data(QDataStream &s, qint64 size) |
215 | { |
216 | // Skip mode data. |
217 | for (qint32 i32 = 0; size; size -= i32) { |
218 | i32 = std::min(a: size, b: qint64(std::numeric_limits<qint32>::max())); |
219 | i32 = s.skipRawData(len: i32); |
220 | if (i32 < 1) |
221 | return false; |
222 | } |
223 | return true; |
224 | } |
225 | |
226 | static bool skip_section(QDataStream &s, bool psb = false) |
227 | { |
228 | auto section_length = readSize(s, psb); |
229 | if (section_length < 0) |
230 | return false; |
231 | return skip_data(s, size: section_length); |
232 | } |
233 | |
234 | /*! |
235 | * \brief readPascalString |
236 | * Reads the Pascal string as defined in the PSD specification. |
237 | * \param s The stream. |
238 | * \param alignBytes Alignment of the string. |
239 | * \param size Number of stream bytes used. |
240 | * \return The string read. |
241 | */ |
242 | static QString readPascalString(QDataStream &s, qint32 alignBytes = 1, qint32 *size = nullptr) |
243 | { |
244 | qint32 tmp = 0; |
245 | if (size == nullptr) |
246 | size = &tmp; |
247 | |
248 | quint8 stringSize; |
249 | s >> stringSize; |
250 | *size = sizeof(stringSize); |
251 | |
252 | QString str; |
253 | if (stringSize > 0) { |
254 | QByteArray ba; |
255 | ba.resize(size: stringSize); |
256 | auto read = s.readRawData(ba.data(), len: ba.size()); |
257 | if (read > 0) { |
258 | *size += read; |
259 | str = QString::fromLatin1(ba); |
260 | } |
261 | } |
262 | |
263 | // align |
264 | if (alignBytes > 1) |
265 | if (auto pad = *size % alignBytes) |
266 | *size += s.skipRawData(len: alignBytes - pad); |
267 | |
268 | return str; |
269 | } |
270 | |
271 | /*! |
272 | * \brief readImageResourceSection |
273 | * Reads the image resource section. |
274 | * \param s The stream. |
275 | * \param ok Pointer to the operation result variable. |
276 | * \return The image resource section raw data. |
277 | */ |
278 | static PSDImageResourceSection readImageResourceSection(QDataStream &s, bool *ok = nullptr) |
279 | { |
280 | PSDImageResourceSection irs; |
281 | |
282 | bool tmp = true; |
283 | if (ok == nullptr) |
284 | ok = &tmp; |
285 | *ok = true; |
286 | |
287 | // Section size |
288 | qint32 sectioSize; |
289 | s >> sectioSize; |
290 | |
291 | // Reading Image resource block |
292 | for (auto size = sectioSize; size > 0;) { |
293 | // Length Description |
294 | // ------------------------------------------------------------------- |
295 | // 4 Signature: '8BIM' |
296 | // 2 Unique identifier for the resource. Image resource IDs |
297 | // contains a list of resource IDs used by Photoshop. |
298 | // Variable Name: Pascal string, padded to make the size even |
299 | // (a null name consists of two bytes of 0) |
300 | // 4 Actual size of resource data that follows |
301 | // Variable The resource data, described in the sections on the |
302 | // individual resource types. It is padded to make the size |
303 | // even. |
304 | |
305 | quint32 signature; |
306 | s >> signature; |
307 | size -= sizeof(signature); |
308 | // NOTE: MeSa signature is not documented but found in some old PSD take from Photoshop 7.0 CD. |
309 | if (signature != S_8BIM && signature != S_MeSa) { // 8BIM and MeSa |
310 | qDebug() << "Invalid Image Resource Block Signature!" ; |
311 | *ok = false; |
312 | break; |
313 | } |
314 | |
315 | // id |
316 | quint16 id; |
317 | s >> id; |
318 | size -= sizeof(id); |
319 | |
320 | // getting data |
321 | PSDImageResourceBlock irb; |
322 | |
323 | // name |
324 | qint32 bytes = 0; |
325 | irb.name = readPascalString(s, alignBytes: 2, size: &bytes); |
326 | size -= bytes; |
327 | |
328 | // data read |
329 | quint32 dataSize; |
330 | s >> dataSize; |
331 | size -= sizeof(dataSize); |
332 | // NOTE: Qt device::read() and QDataStream::readRawData() could read less data than specified. |
333 | // The read code should be improved. |
334 | if (auto dev = s.device()) |
335 | irb.data = dev->read(maxlen: dataSize); |
336 | auto read = irb.data.size(); |
337 | if (read > 0) |
338 | size -= read; |
339 | if (quint32(read) != dataSize) { |
340 | qDebug() << "Image Resource Block Read Error!" ; |
341 | *ok = false; |
342 | break; |
343 | } |
344 | |
345 | if (auto pad = dataSize % 2) { |
346 | auto skipped = s.skipRawData(len: pad); |
347 | if (skipped > 0) |
348 | size -= skipped; |
349 | } |
350 | |
351 | // insert IRB |
352 | irs.insert(key: id, value: irb); |
353 | } |
354 | |
355 | return irs; |
356 | } |
357 | |
358 | PSDAdditionalLayerInfo readAdditionalLayer(QDataStream &s, bool *ok = nullptr) |
359 | { |
360 | PSDAdditionalLayerInfo li; |
361 | |
362 | bool tmp = true; |
363 | if (ok == nullptr) |
364 | ok = &tmp; |
365 | |
366 | s >> li.signature; |
367 | *ok = li.signature == S_8BIM || li.signature == S_8B64; |
368 | if (!*ok) |
369 | return li; |
370 | |
371 | s >> li.id; |
372 | *ok = s.status() == QDataStream::Ok; |
373 | if (!*ok) |
374 | return li; |
375 | |
376 | li.size = readSize(s, psb: li.signature == S_8B64); |
377 | *ok = li.size >= 0; |
378 | if (!*ok) |
379 | return li; |
380 | |
381 | *ok = skip_data(s, size: li.size); |
382 | |
383 | return li; |
384 | } |
385 | |
386 | PSDLayerAndMaskSection readLayerAndMaskSection(QDataStream &s, bool isPsb, bool *ok = nullptr) |
387 | { |
388 | PSDLayerAndMaskSection lms; |
389 | |
390 | bool tmp = true; |
391 | if (ok == nullptr) |
392 | ok = &tmp; |
393 | *ok = true; |
394 | |
395 | auto device = s.device(); |
396 | device->startTransaction(); |
397 | |
398 | lms.size = readSize(s, psb: isPsb); |
399 | |
400 | // read layer info |
401 | if (s.status() == QDataStream::Ok && !lms.atEnd(isPsb)) { |
402 | lms.layerInfo.size = readSize(s, psb: isPsb); |
403 | if (lms.layerInfo.size > 0) { |
404 | s >> lms.layerInfo.layerCount; |
405 | skip_data(s, size: lms.layerInfo.size - sizeof(lms.layerInfo.layerCount)); |
406 | } |
407 | } |
408 | |
409 | // read global layer mask info |
410 | if (s.status() == QDataStream::Ok && !lms.atEnd(isPsb)) { |
411 | lms.globalLayerMaskInfo.size = readSize(s, psb: false); // always 32-bits |
412 | if (lms.globalLayerMaskInfo.size > 0) { |
413 | skip_data(s, size: lms.globalLayerMaskInfo.size); |
414 | } |
415 | } |
416 | |
417 | // read additional layer info |
418 | if (s.status() == QDataStream::Ok) { |
419 | for (bool ok = true; ok && !lms.atEnd(isPsb);) { |
420 | auto al = readAdditionalLayer(s, ok: &ok); |
421 | if (ok) |
422 | lms.additionalLayerInfo.insert(key: al.id, value: al); |
423 | } |
424 | } |
425 | |
426 | device->rollbackTransaction(); |
427 | *ok = skip_section(s, psb: isPsb); |
428 | return lms; |
429 | } |
430 | |
431 | /*! |
432 | * \brief readColorModeDataSection |
433 | * Read the color mode section |
434 | * \param s The stream. |
435 | * \param ok Pointer to the operation result variable. |
436 | * \return The color mode section. |
437 | */ |
438 | PSDColorModeDataSection readColorModeDataSection(QDataStream &s, bool *ok = nullptr) |
439 | { |
440 | PSDColorModeDataSection cms; |
441 | |
442 | bool tmp = false; |
443 | if (ok == nullptr) |
444 | ok = &tmp; |
445 | *ok = true; |
446 | |
447 | qint32 size; |
448 | s >> size; |
449 | if (size != 768) { // read the duotone data (524 bytes) |
450 | // NOTE: A RGB/Gray float image has a 112 bytes ColorModeData that could be |
451 | // the "32-bit Toning Options" of Photoshop (starts with 'hdrt'). |
452 | // Official Adobe specification tells "Only indexed color and duotone |
453 | // (see the mode field in the File header section) have color mode data.". |
454 | // See test case images 32bit_grayscale.psd and 32bit-rgb.psd |
455 | cms.duotone.data = s.device()->read(maxlen: size); |
456 | if (cms.duotone.data.size() != size) |
457 | *ok = false; |
458 | } |
459 | else { // read the palette (768 bytes) |
460 | auto&& palette = cms.palette; |
461 | QList<quint8> vect(size); |
462 | for (auto&& v : vect) |
463 | s >> v; |
464 | for (qsizetype i = 0, n = vect.size()/3; i < n; ++i) |
465 | palette.append(t: qRgb(r: vect.at(i), g: vect.at(i: n+i), b: vect.at(i: n+n+i))); |
466 | } |
467 | |
468 | return cms; |
469 | } |
470 | |
471 | /*! |
472 | * \brief setColorSpace |
473 | * Set the color space to the image. |
474 | * \param img The image. |
475 | * \param irs The image resource section. |
476 | * \return True on success, otherwise false. |
477 | */ |
478 | static bool setColorSpace(QImage& img, const PSDImageResourceSection& irs) |
479 | { |
480 | if (!irs.contains(key: IRI_ICCPROFILE)) |
481 | return false; |
482 | auto irb = irs.value(key: IRI_ICCPROFILE); |
483 | auto cs = QColorSpace::fromIccProfile(iccProfile: irb.data); |
484 | if (!cs.isValid()) |
485 | return false; |
486 | img.setColorSpace(cs); |
487 | return true; |
488 | } |
489 | |
490 | /*! |
491 | * \brief setXmpData |
492 | * Adds XMP metadata to QImage. |
493 | * \param img The image. |
494 | * \param irs The image resource section. |
495 | * \return True on success, otherwise false. |
496 | */ |
497 | static bool setXmpData(QImage& img, const PSDImageResourceSection& irs) |
498 | { |
499 | if (!irs.contains(key: IRI_XMPMETADATA)) |
500 | return false; |
501 | auto irb = irs.value(key: IRI_XMPMETADATA); |
502 | auto xmp = QString::fromUtf8(ba: irb.data); |
503 | if (xmp.isEmpty()) |
504 | return false; |
505 | // NOTE: "XML:com.adobe.xmp" is the meta set by Qt reader when an |
506 | // XMP packet is found (e.g. when reading a PNG saved by Photoshop). |
507 | // I'm reusing the same key because a programs could search for it. |
508 | img.setText(QStringLiteral("XML:com.adobe.xmp" ), value: xmp); |
509 | return true; |
510 | } |
511 | |
512 | /*! |
513 | * \brief hasMergedData |
514 | * Checks if merged image data are available. |
515 | * \param irs The image resource section. |
516 | * \return True on success or if the block does not exist, otherwise false. |
517 | */ |
518 | static bool hasMergedData(const PSDImageResourceSection& irs) |
519 | { |
520 | if (!irs.contains(key: IRI_VERSIONINFO)) |
521 | return true; |
522 | auto irb = irs.value(key: IRI_VERSIONINFO); |
523 | if (irb.data.size() > 4) |
524 | return irb.data.at(i: 4) != 0; |
525 | return false; |
526 | } |
527 | |
528 | /*! |
529 | * \brief setResolution |
530 | * Set the image resolution. |
531 | * \param img The image. |
532 | * \param irs The image resource section. |
533 | * \return True on success, otherwise false. |
534 | */ |
535 | static bool setResolution(QImage& img, const PSDImageResourceSection& irs) |
536 | { |
537 | if (!irs.contains(key: IRI_RESOLUTIONINFO)) |
538 | return false; |
539 | auto irb = irs.value(key: IRI_RESOLUTIONINFO); |
540 | |
541 | QDataStream s(irb.data); |
542 | s.setByteOrder(QDataStream::BigEndian); |
543 | |
544 | qint32 i32; |
545 | s >> i32; // Horizontal resolution in pixels per inch. |
546 | if (i32 <= 0) |
547 | return false; |
548 | auto hres = fixedPointToDouble(fixedPoint: i32); |
549 | |
550 | s.skipRawData(len: 4); // Display data (not used here) |
551 | |
552 | s >> i32; // Vertial resolution in pixels per inch. |
553 | if (i32 <= 0) |
554 | return false; |
555 | auto vres = fixedPointToDouble(fixedPoint: i32); |
556 | |
557 | img.setDotsPerMeterX(hres * 1000 / 25.4); |
558 | img.setDotsPerMeterY(vres * 1000 / 25.4); |
559 | return true; |
560 | } |
561 | |
562 | /*! |
563 | * \brief setTransparencyIndex |
564 | * Search for transparency index block and, if found, changes the alpha of the value at the given index. |
565 | * \param img The image. |
566 | * \param irs The image resource section. |
567 | * \return True on success, otherwise false. |
568 | */ |
569 | static bool setTransparencyIndex(QImage& img, const PSDImageResourceSection& irs) |
570 | { |
571 | if (!irs.contains(key: IRI_TRANSPARENCYINDEX)) |
572 | return false; |
573 | auto irb = irs.value(key: IRI_TRANSPARENCYINDEX); |
574 | QDataStream s(irb.data); |
575 | s.setByteOrder(QDataStream::BigEndian); |
576 | quint16 idx; |
577 | s >> idx; |
578 | |
579 | auto palette = img.colorTable(); |
580 | if (idx < palette.size()) { |
581 | auto&& v = palette[idx]; |
582 | v = QRgb(v & ~0xFF000000); |
583 | img.setColorTable(palette); |
584 | return true; |
585 | } |
586 | |
587 | return false; |
588 | } |
589 | |
590 | static QDataStream &(QDataStream &s, PSDHeader &) |
591 | { |
592 | s >> header.signature; |
593 | s >> header.version; |
594 | for (int i = 0; i < 6; i++) { |
595 | s >> header.reserved[i]; |
596 | } |
597 | s >> header.channel_count; |
598 | s >> header.height; |
599 | s >> header.width; |
600 | s >> header.depth; |
601 | s >> header.color_mode; |
602 | return s; |
603 | } |
604 | |
605 | // Check that the header is a valid PSD (as written in the PSD specification). |
606 | static bool (const PSDHeader &) |
607 | { |
608 | if (header.signature != 0x38425053) { // '8BPS' |
609 | //qDebug() << "PSD header: invalid signature" << header.signature; |
610 | return false; |
611 | } |
612 | if (header.version != 1 && header.version != 2) { |
613 | qDebug() << "PSD header: invalid version" << header.version; |
614 | return false; |
615 | } |
616 | if (header.depth != 8 && |
617 | header.depth != 16 && |
618 | header.depth != 32 && |
619 | header.depth != 1) { |
620 | qDebug() << "PSD header: invalid depth" << header.depth; |
621 | return false; |
622 | } |
623 | if (header.color_mode != CM_RGB && |
624 | header.color_mode != CM_GRAYSCALE && |
625 | header.color_mode != CM_INDEXED && |
626 | header.color_mode != CM_DUOTONE && |
627 | header.color_mode != CM_CMYK && |
628 | header.color_mode != CM_LABCOLOR && |
629 | header.color_mode != CM_MULTICHANNEL && |
630 | header.color_mode != CM_BITMAP) { |
631 | qDebug() << "PSD header: invalid color mode" << header.color_mode; |
632 | return false; |
633 | } |
634 | // Specs tells: "Supported range is 1 to 56" but when the alpha channel is present the limit is 57: |
635 | // Photoshop does not make you add more (see also 53alphas.psd test case). |
636 | if (header.channel_count < 1 || header.channel_count > 57) { |
637 | qDebug() << "PSD header: invalid number of channels" << header.channel_count; |
638 | return false; |
639 | } |
640 | if (header.width > 300000 || header.height > 300000) { |
641 | qDebug() << "PSD header: invalid image size" << header.width << "x" << header.height; |
642 | return false; |
643 | } |
644 | return true; |
645 | } |
646 | |
647 | // Check that the header is supported by this plugin. |
648 | static bool (const PSDHeader &) |
649 | { |
650 | if (!IsValid(header)) { |
651 | return false; |
652 | } |
653 | if (header.version != 1 && header.version != 2) { |
654 | return false; |
655 | } |
656 | if (header.depth != 8 && |
657 | header.depth != 16 && |
658 | header.depth != 32 && |
659 | header.depth != 1) { |
660 | return false; |
661 | } |
662 | if (header.color_mode != CM_RGB && |
663 | header.color_mode != CM_GRAYSCALE && |
664 | header.color_mode != CM_INDEXED && |
665 | header.color_mode != CM_DUOTONE && |
666 | header.color_mode != CM_CMYK && |
667 | header.color_mode != CM_MULTICHANNEL && |
668 | header.color_mode != CM_LABCOLOR && |
669 | header.color_mode != CM_BITMAP) { |
670 | return false; |
671 | } |
672 | if (header.color_mode == CM_MULTICHANNEL && |
673 | header.channel_count < 3) { |
674 | return false; |
675 | } |
676 | return true; |
677 | } |
678 | |
679 | /*! |
680 | * \brief decompress |
681 | * Fast PackBits decompression. |
682 | * \param input The compressed input buffer. |
683 | * \param ilen The input buffer size. |
684 | * \param output The uncompressed target buffer. |
685 | * \param olen The target buffer size. |
686 | * \return The number of valid bytes in the target buffer. |
687 | */ |
688 | qint64 decompress(const char *input, qint64 ilen, char *output, qint64 olen) |
689 | { |
690 | qint64 j = 0; |
691 | for (qint64 ip = 0, rr = 0, available = olen; j < olen && ip < ilen; available = olen - j) { |
692 | signed char n = static_cast<signed char>(input[ip++]); |
693 | if (n == -128) |
694 | continue; |
695 | |
696 | if (n >= 0) { |
697 | rr = qint64(n) + 1; |
698 | if (available < rr) { |
699 | --ip; |
700 | break; |
701 | } |
702 | |
703 | if (ip + rr > ilen) |
704 | return -1; |
705 | memcpy(dest: output + j, src: input + ip, n: size_t(rr)); |
706 | ip += rr; |
707 | } |
708 | else if (ip < ilen) { |
709 | rr = qint64(1-n); |
710 | if (available < rr) { |
711 | --ip; |
712 | break; |
713 | } |
714 | memset(s: output + j, c: input[ip++], n: size_t(rr)); |
715 | } |
716 | |
717 | j += rr; |
718 | } |
719 | return j; |
720 | } |
721 | |
722 | /*! |
723 | * \brief imageFormat |
724 | * \param header The PSD header. |
725 | * \return The Qt image format. |
726 | */ |
727 | static QImage::Format (const PSDHeader &, bool alpha) |
728 | { |
729 | if (header.channel_count == 0) { |
730 | return QImage::Format_Invalid; |
731 | } |
732 | |
733 | auto format = QImage::Format_Invalid; |
734 | switch(header.color_mode) { |
735 | case CM_RGB: |
736 | if (header.depth == 32) |
737 | format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX32FPx4 : QImage::Format_RGBA32FPx4_Premultiplied; |
738 | else if (header.depth == 16) |
739 | format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64_Premultiplied; |
740 | else |
741 | format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888_Premultiplied; |
742 | break; |
743 | case CM_MULTICHANNEL: // Treat MCH as CMYK (number of channel check is done in IsSupported()) |
744 | case CM_CMYK: // Photoshop supports CMYK/MCH 8-bits and 16-bits only |
745 | if (header.depth == 16) |
746 | format = header.channel_count < 5 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64; |
747 | else if (header.depth == 8) |
748 | format = header.channel_count < 5 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888; |
749 | break; |
750 | case CM_LABCOLOR: // Photoshop supports LAB 8-bits and 16-bits only |
751 | if (header.depth == 16) |
752 | format = header.channel_count < 4 || !alpha ? QImage::Format_RGBX64 : QImage::Format_RGBA64; |
753 | else if (header.depth == 8) |
754 | format = header.channel_count < 4 || !alpha ? QImage::Format_RGB888 : QImage::Format_RGBA8888; |
755 | break; |
756 | case CM_GRAYSCALE: |
757 | case CM_DUOTONE: |
758 | format = header.depth == 8 ? QImage::Format_Grayscale8 : QImage::Format_Grayscale16; |
759 | break; |
760 | case CM_INDEXED: |
761 | format = header.depth == 8 ? QImage::Format_Indexed8 : QImage::Format_Invalid; |
762 | break; |
763 | case CM_BITMAP: |
764 | format = header.depth == 1 ? QImage::Format_Mono : QImage::Format_Invalid; |
765 | break; |
766 | } |
767 | return format; |
768 | } |
769 | |
770 | /*! |
771 | * \brief imageChannels |
772 | * \param format The Qt image format. |
773 | * \return The number of channels of the image format. |
774 | */ |
775 | static qint32 imageChannels(const QImage::Format& format) |
776 | { |
777 | qint32 c = 4; |
778 | switch(format) { |
779 | case QImage::Format_RGB888: |
780 | c = 3; |
781 | break; |
782 | case QImage::Format_Grayscale8: |
783 | case QImage::Format_Grayscale16: |
784 | case QImage::Format_Indexed8: |
785 | case QImage::Format_Mono: |
786 | c = 1; |
787 | break; |
788 | default: |
789 | break; |
790 | } |
791 | return c; |
792 | } |
793 | |
794 | inline quint8 xchg(quint8 v) |
795 | { |
796 | return v; |
797 | } |
798 | |
799 | inline quint16 xchg(quint16 v) |
800 | { |
801 | #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN |
802 | return quint16( (v>>8) | (v<<8) ); |
803 | #else |
804 | return v; // never tested |
805 | #endif |
806 | } |
807 | |
808 | inline quint32 xchg(quint32 v) |
809 | { |
810 | #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN |
811 | return quint32( (v>>24) | ((v & 0x00FF0000)>>8) | ((v & 0x0000FF00)<<8) | (v<<24) ); |
812 | #else |
813 | return v; // never tested |
814 | #endif |
815 | } |
816 | |
817 | inline float xchg(float v) |
818 | { |
819 | #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN |
820 | # ifdef Q_CC_MSVC |
821 | float *pf = &v; |
822 | quint32 f = xchg(*reinterpret_cast<quint32*>(pf)); |
823 | quint32 *pi = &f; |
824 | return *reinterpret_cast<float*>(pi); |
825 | # else |
826 | quint32 t; |
827 | std::memcpy(dest: &t, src: &v, n: sizeof(quint32)); |
828 | t = xchg(v: t); |
829 | std::memcpy(dest: &v, src: &t, n: sizeof(quint32)); |
830 | return v; |
831 | # endif |
832 | #else |
833 | return v; // never tested |
834 | #endif |
835 | } |
836 | |
837 | template<class T> |
838 | inline void planarToChunchy(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn) |
839 | { |
840 | auto s = reinterpret_cast<const T*>(source); |
841 | auto t = reinterpret_cast<T*>(target); |
842 | for (qint32 x = 0; x < width; ++x) { |
843 | t[x * cn + c] = xchg(s[x]); |
844 | } |
845 | } |
846 | |
847 | template<class T> |
848 | inline void planarToChunchyFloatToUInt16(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn) |
849 | { |
850 | auto s = reinterpret_cast<const T*>(source); |
851 | auto t = reinterpret_cast<quint16*>(target); |
852 | for (qint32 x = 0; x < width; ++x) { |
853 | t[x * cn + c] = quint16(std::min(xchg(s[x]) * std::numeric_limits<quint16>::max() + 0.5, double(std::numeric_limits<quint16>::max()))); |
854 | } |
855 | } |
856 | |
857 | enum class PremulConversion { |
858 | PS2P, // Photoshop premul to qimage premul (required by RGB) |
859 | PS2A, // Photoshop premul to unassociated alpha (required by RGB, CMYK and L* components of LAB) |
860 | PSLab2A // Photoshop premul to unassociated alpha (required by a* and b* components of LAB) |
861 | }; |
862 | |
863 | template<class T> |
864 | inline void premulConversion(char *stride, qint32 width, qint32 ac, qint32 cn, const PremulConversion &conv) |
865 | { |
866 | auto s = reinterpret_cast<T*>(stride); |
867 | // NOTE: to avoid overflows, max is casted to qint64: that is possible because max is always an integer (even if T is float) |
868 | auto max = qint64(std::numeric_limits<T>::is_integer ? std::numeric_limits<T>::max() : 1); |
869 | |
870 | for (qint32 c = 0; c < ac; ++c) { |
871 | if (conv == PremulConversion::PS2P) { |
872 | for (qint32 x = 0; x < width; ++x) { |
873 | auto xcn = x * cn; |
874 | auto alpha = *(s + xcn + ac); |
875 | *(s + xcn + c) = *(s + xcn + c) + alpha - max; |
876 | } |
877 | } |
878 | else if (conv == PremulConversion::PS2A || (conv == PremulConversion::PSLab2A && c == 0)) { |
879 | for (qint32 x = 0; x < width; ++x) { |
880 | auto xcn = x * cn; |
881 | auto alpha = *(s + xcn + ac); |
882 | if (alpha > 0) |
883 | *(s + xcn + c) = ((*(s + xcn + c) + alpha - max) * max + alpha / 2) / alpha; |
884 | } |
885 | } |
886 | else if (conv == PremulConversion::PSLab2A) { |
887 | for (qint32 x = 0; x < width; ++x) { |
888 | auto xcn = x * cn; |
889 | auto alpha = *(s + xcn + ac); |
890 | if (alpha > 0) |
891 | *(s + xcn + c) = ((*(s + xcn + c) + (alpha - max + 1) / 2) * max + alpha / 2) / alpha; |
892 | } |
893 | } |
894 | } |
895 | } |
896 | |
897 | inline void monoInvert(uchar *target, const char* source, qint32 bytes) |
898 | { |
899 | auto s = reinterpret_cast<const quint8*>(source); |
900 | auto t = reinterpret_cast<quint8*>(target); |
901 | for (qint32 x = 0; x < bytes; ++x) { |
902 | t[x] = ~s[x]; |
903 | } |
904 | } |
905 | |
906 | template<class T> |
907 | inline void rawChannelsCopy(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width) |
908 | { |
909 | auto s = reinterpret_cast<const T*>(source); |
910 | auto t = reinterpret_cast<T*>(target); |
911 | for (qint32 c = 0, cs = std::min(a: targetChannels, b: sourceChannels); c < cs; ++c) { |
912 | for (qint32 x = 0; x < width; ++x) { |
913 | t[x * targetChannels + c] = s[x * sourceChannels + c]; |
914 | } |
915 | } |
916 | } |
917 | |
918 | template<class T> |
919 | inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false) |
920 | { |
921 | auto s = reinterpret_cast<const T*>(source); |
922 | auto t = reinterpret_cast<T*>(target); |
923 | auto max = double(std::numeric_limits<T>::max()); |
924 | auto invmax = 1.0 / max; // speed improvements by ~10% |
925 | |
926 | if (sourceChannels < 3) { |
927 | qDebug() << "cmykToRgb: image is not a valid CMY/CMYK!" ; |
928 | return; |
929 | } |
930 | |
931 | for (qint32 w = 0; w < width; ++w) { |
932 | auto ps = s + sourceChannels * w; |
933 | auto C = 1 - *(ps + 0) * invmax; |
934 | auto M = 1 - *(ps + 1) * invmax; |
935 | auto Y = 1 - *(ps + 2) * invmax; |
936 | auto K = sourceChannels > 3 ? 1 - *(ps + 3) * invmax : 0.0; |
937 | |
938 | auto pt = t + targetChannels * w; |
939 | *(pt + 0) = T(std::min(max - (C * (1 - K) + K) * max + 0.5, max)); |
940 | *(pt + 1) = T(std::min(max - (M * (1 - K) + K) * max + 0.5, max)); |
941 | *(pt + 2) = T(std::min(max - (Y * (1 - K) + K) * max + 0.5, max)); |
942 | if (targetChannels == 4) { |
943 | if (sourceChannels >= 5 && alpha) |
944 | *(pt + 3) = *(ps + 4); |
945 | else |
946 | *(pt + 3) = std::numeric_limits<T>::max(); |
947 | } |
948 | } |
949 | } |
950 | |
951 | inline double finv(double v) |
952 | { |
953 | return (v > 6.0 / 29.0 ? v * v * v : (v - 16.0 / 116.0) / 7.787); |
954 | } |
955 | |
956 | inline double gammaCorrection(double linear) |
957 | { |
958 | #ifdef PSD_FAST_LAB_CONVERSION |
959 | return linear; |
960 | #else |
961 | // Replacing fastPow with std::pow the conversion time is 2/3 times longer: using fastPow |
962 | // there are minimal differences in the conversion that are not visually noticeable. |
963 | return (linear > 0.0031308 ? 1.055 * fastPow(x: linear, y: 1.0 / 2.4) - 0.055 : 12.92 * linear); |
964 | #endif |
965 | } |
966 | |
967 | template<class T> |
968 | inline void labToRgb(uchar *target, qint32 targetChannels, const char *source, qint32 sourceChannels, qint32 width, bool alpha = false) |
969 | { |
970 | auto s = reinterpret_cast<const T*>(source); |
971 | auto t = reinterpret_cast<T*>(target); |
972 | auto max = double(std::numeric_limits<T>::max()); |
973 | auto invmax = 1.0 / max; |
974 | |
975 | if (sourceChannels < 3) { |
976 | qDebug() << "labToRgb: image is not a valid LAB!" ; |
977 | return; |
978 | } |
979 | |
980 | for (qint32 w = 0; w < width; ++w) { |
981 | auto ps = s + sourceChannels * w; |
982 | auto L = (*(ps + 0) * invmax) * 100.0; |
983 | auto A = (*(ps + 1) * invmax) * 255.0 - 128.0; |
984 | auto B = (*(ps + 2) * invmax) * 255.0 - 128.0; |
985 | |
986 | // converting LAB to XYZ (D65 illuminant) |
987 | auto Y = (L + 16.0) * (1.0 / 116.0); |
988 | auto X = A * (1.0 / 500.0) + Y; |
989 | auto Z = Y - B * (1.0 / 200.0); |
990 | |
991 | // NOTE: use the constants of the illuminant of the target RGB color space |
992 | X = finv(X) * 0.9504; // D50: * 0.9642 |
993 | Y = finv(Y) * 1.0000; // D50: * 1.0000 |
994 | Z = finv(Z) * 1.0888; // D50: * 0.8251 |
995 | |
996 | // converting XYZ to sRGB (sRGB illuminant is D65) |
997 | auto r = gammaCorrection( 3.24071 * X - 1.53726 * Y - 0.498571 * Z); |
998 | auto g = gammaCorrection(- 0.969258 * X + 1.87599 * Y + 0.0415557 * Z); |
999 | auto b = gammaCorrection( 0.0556352 * X - 0.203996 * Y + 1.05707 * Z); |
1000 | |
1001 | auto pt = t + targetChannels * w; |
1002 | *(pt + 0) = T(std::max(std::min(r * max + 0.5, max), 0.0)); |
1003 | *(pt + 1) = T(std::max(std::min(g * max + 0.5, max), 0.0)); |
1004 | *(pt + 2) = T(std::max(std::min(b * max + 0.5, max), 0.0)); |
1005 | if (targetChannels == 4) { |
1006 | if (sourceChannels >= 4 && alpha) |
1007 | *(pt + 3) = *(ps + 3); |
1008 | else |
1009 | *(pt + 3) = std::numeric_limits<T>::max(); |
1010 | } |
1011 | } |
1012 | } |
1013 | |
1014 | bool readChannel(QByteArray& target, QDataStream &stream, quint32 compressedSize, quint16 compression) |
1015 | { |
1016 | if (compression) { |
1017 | if (compressedSize > kMaxQVectorSize) { |
1018 | return false; |
1019 | } |
1020 | QByteArray tmp; |
1021 | tmp.resize(size: compressedSize); |
1022 | if (stream.readRawData(tmp.data(), len: tmp.size()) != tmp.size()) { |
1023 | return false; |
1024 | } |
1025 | if (decompress(input: tmp.data(), ilen: tmp.size(), output: target.data(), olen: target.size()) < 0) { |
1026 | return false; |
1027 | } |
1028 | } |
1029 | else if (stream.readRawData(target.data(), len: target.size()) != target.size()) { |
1030 | return false; |
1031 | } |
1032 | |
1033 | return stream.status() == QDataStream::Ok; |
1034 | } |
1035 | |
1036 | // Load the PSD image. |
1037 | static bool (QDataStream &stream, const PSDHeader &, QImage &img) |
1038 | { |
1039 | // Checking for PSB |
1040 | auto isPsb = header.version == 2; |
1041 | bool ok = false; |
1042 | |
1043 | // Color Mode Data section |
1044 | auto cmds = readColorModeDataSection(s&: stream, ok: &ok); |
1045 | if (!ok) { |
1046 | qDebug() << "Error while skipping Color Mode Data section" ; |
1047 | return false; |
1048 | } |
1049 | |
1050 | // Image Resources Section |
1051 | auto irs = readImageResourceSection(s&: stream, ok: &ok); |
1052 | if (!ok) { |
1053 | qDebug() << "Error while reading Image Resources Section" ; |
1054 | return false; |
1055 | } |
1056 | // Checking for merged image (Photoshop compatibility data) |
1057 | if (!hasMergedData(irs)) { |
1058 | qDebug() << "No merged data found" ; |
1059 | return false; |
1060 | } |
1061 | |
1062 | // Layer and Mask section |
1063 | auto lms = readLayerAndMaskSection(s&: stream, isPsb, ok: &ok); |
1064 | if (!ok) { |
1065 | qDebug() << "Error while skipping Layer and Mask section" ; |
1066 | return false; |
1067 | } |
1068 | |
1069 | // Find out if the data is compressed. |
1070 | // Known values: |
1071 | // 0: no compression |
1072 | // 1: RLE compressed |
1073 | quint16 compression; |
1074 | stream >> compression; |
1075 | if (compression > 1) { |
1076 | qDebug() << "Unknown compression type" ; |
1077 | return false; |
1078 | } |
1079 | |
1080 | // Try to identify the nature of spots: note that this is just one of many ways to identify the presence |
1081 | // of alpha channels: should work in most cases where colorspaces != RGB/Gray |
1082 | auto alpha = header.color_mode == CM_RGB; |
1083 | if (!lms.isNull()) |
1084 | alpha = lms.hasAlpha(); |
1085 | |
1086 | const QImage::Format format = imageFormat(header, alpha); |
1087 | if (format == QImage::Format_Invalid) { |
1088 | qWarning() << "Unsupported image format. color_mode:" << header.color_mode << "depth:" << header.depth << "channel_count:" << header.channel_count; |
1089 | return false; |
1090 | } |
1091 | |
1092 | img = imageAlloc(width: header.width, height: header.height, format); |
1093 | if (img.isNull()) { |
1094 | qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height); |
1095 | return false; |
1096 | } |
1097 | img.fill(pixel: qRgb(r: 0, g: 0, b: 0)); |
1098 | if (!cmds.palette.isEmpty()) { |
1099 | img.setColorTable(cmds.palette); |
1100 | setTransparencyIndex(img, irs); |
1101 | } |
1102 | |
1103 | auto imgChannels = imageChannels(format: img.format()); |
1104 | auto channel_num = std::min(a: qint32(header.channel_count), b: imgChannels); |
1105 | auto raw_count = qsizetype(header.width * header.depth + 7) / 8; |
1106 | |
1107 | if (header.height > kMaxQVectorSize / header.channel_count / sizeof(quint32)) { |
1108 | qWarning() << "LoadPSD() header height/channel_count too big" << header.height << header.channel_count; |
1109 | return false; |
1110 | } |
1111 | |
1112 | QList<quint32> strides(header.height * header.channel_count, raw_count); |
1113 | // Read the compressed stride sizes |
1114 | if (compression) { |
1115 | for (auto&& v : strides) { |
1116 | if (isPsb) { |
1117 | stream >> v; |
1118 | continue; |
1119 | } |
1120 | quint16 tmp; |
1121 | stream >> tmp; |
1122 | v = tmp; |
1123 | } |
1124 | } |
1125 | // calculate the absolute file positions of each stride (required when a colorspace conversion should be done) |
1126 | auto device = stream.device(); |
1127 | QList<quint64> stridePositions(strides.size()); |
1128 | if (!stridePositions.isEmpty()) { |
1129 | stridePositions[0] = device->pos(); |
1130 | } |
1131 | for (qsizetype i = 1, n = stridePositions.size(); i < n; ++i) { |
1132 | stridePositions[i] = stridePositions[i-1] + strides.at(i: i-1); |
1133 | } |
1134 | |
1135 | // Read the image |
1136 | QByteArray rawStride; |
1137 | rawStride.resize(size: raw_count); |
1138 | |
1139 | // clang-format off |
1140 | // checks the need of color conversion (that requires random access to the image) |
1141 | auto randomAccess = (header.color_mode == CM_CMYK) || |
1142 | (header.color_mode == CM_LABCOLOR) || |
1143 | (header.color_mode == CM_MULTICHANNEL) || |
1144 | (header.color_mode != CM_INDEXED && img.hasAlphaChannel()); |
1145 | // clang-format on |
1146 | |
1147 | if (randomAccess) { |
1148 | // In order to make a colorspace transformation, we need all channels of a scanline |
1149 | QByteArray psdScanline; |
1150 | psdScanline.resize(size: qsizetype(header.width * header.depth * header.channel_count + 7) / 8); |
1151 | for (qint32 y = 0, h = header.height; y < h; ++y) { |
1152 | for (qint32 c = 0; c < header.channel_count; ++c) { |
1153 | auto strideNumber = c * qsizetype(h) + y; |
1154 | if (!device->seek(pos: stridePositions.at(i: strideNumber))) { |
1155 | qDebug() << "Error while seeking the stream of channel" << c << "line" << y; |
1156 | return false; |
1157 | } |
1158 | auto&& strideSize = strides.at(i: strideNumber); |
1159 | if (!readChannel(target&: rawStride, stream, compressedSize: strideSize, compression)) { |
1160 | qDebug() << "Error while reading the stream of channel" << c << "line" << y; |
1161 | return false; |
1162 | } |
1163 | |
1164 | auto scanLine = reinterpret_cast<unsigned char*>(psdScanline.data()); |
1165 | if (header.depth == 8) { |
1166 | planarToChunchy<quint8>(target: scanLine, source: rawStride.data(), width: header.width, c, cn: header.channel_count); |
1167 | } |
1168 | else if (header.depth == 16) { |
1169 | planarToChunchy<quint16>(target: scanLine, source: rawStride.data(), width: header.width, c, cn: header.channel_count); |
1170 | } |
1171 | else if (header.depth == 32) { |
1172 | planarToChunchy<float>(target: scanLine, source: rawStride.data(), width: header.width, c, cn: header.channel_count); |
1173 | } |
1174 | } |
1175 | |
1176 | // Convert premultiplied data to unassociated data |
1177 | if (img.hasAlphaChannel()) { |
1178 | auto scanLine = reinterpret_cast<char*>(psdScanline.data()); |
1179 | if (header.color_mode == CM_CMYK) { |
1180 | if (header.depth == 8) |
1181 | premulConversion<quint8>(stride: scanLine, width: header.width, ac: 4, cn: header.channel_count, conv: PremulConversion::PS2A); |
1182 | else if (header.depth == 16) |
1183 | premulConversion<quint16>(stride: scanLine, width: header.width, ac: 4, cn: header.channel_count, conv: PremulConversion::PS2A); |
1184 | } |
1185 | if (header.color_mode == CM_LABCOLOR) { |
1186 | if (header.depth == 8) |
1187 | premulConversion<quint8>(stride: scanLine, width: header.width, ac: 3, cn: header.channel_count, conv: PremulConversion::PSLab2A); |
1188 | else if (header.depth == 16) |
1189 | premulConversion<quint16>(stride: scanLine, width: header.width, ac: 3, cn: header.channel_count, conv: PremulConversion::PSLab2A); |
1190 | } |
1191 | if (header.color_mode == CM_RGB) { |
1192 | if (header.depth == 8) |
1193 | premulConversion<quint8>(stride: scanLine, width: header.width, ac: 3, cn: header.channel_count, conv: PremulConversion::PS2P); |
1194 | else if (header.depth == 16) |
1195 | premulConversion<quint16>(stride: scanLine, width: header.width, ac: 3, cn: header.channel_count, conv: PremulConversion::PS2P); |
1196 | else if (header.depth == 32) |
1197 | premulConversion<float>(stride: scanLine, width: header.width, ac: 3, cn: header.channel_count, conv: PremulConversion::PS2P); |
1198 | } |
1199 | } |
1200 | |
1201 | // Conversion to RGB |
1202 | if (header.color_mode == CM_CMYK || header.color_mode == CM_MULTICHANNEL) { |
1203 | if (header.depth == 8) |
1204 | cmykToRgb<quint8>(target: img.scanLine(y), targetChannels: imgChannels, source: psdScanline.data(), sourceChannels: header.channel_count, width: header.width, alpha); |
1205 | else if (header.depth == 16) |
1206 | cmykToRgb<quint16>(target: img.scanLine(y), targetChannels: imgChannels, source: psdScanline.data(), sourceChannels: header.channel_count, width: header.width, alpha); |
1207 | } |
1208 | if (header.color_mode == CM_LABCOLOR) { |
1209 | if (header.depth == 8) |
1210 | labToRgb<quint8>(target: img.scanLine(y), targetChannels: imgChannels, source: psdScanline.data(), sourceChannels: header.channel_count, width: header.width, alpha); |
1211 | else if (header.depth == 16) |
1212 | labToRgb<quint16>(target: img.scanLine(y), targetChannels: imgChannels, source: psdScanline.data(), sourceChannels: header.channel_count, width: header.width, alpha); |
1213 | } |
1214 | if (header.color_mode == CM_RGB) { |
1215 | if (header.depth == 8) |
1216 | rawChannelsCopy<quint8>(target: img.scanLine(y), targetChannels: imgChannels, source: psdScanline.data(), sourceChannels: header.channel_count, width: header.width); |
1217 | else if (header.depth == 16) |
1218 | rawChannelsCopy<quint16>(target: img.scanLine(y), targetChannels: imgChannels, source: psdScanline.data(), sourceChannels: header.channel_count, width: header.width); |
1219 | else if (header.depth == 32) |
1220 | rawChannelsCopy<float>(target: img.scanLine(y), targetChannels: imgChannels, source: psdScanline.data(), sourceChannels: header.channel_count, width: header.width); |
1221 | } |
1222 | } |
1223 | } |
1224 | else { |
1225 | // Linear read (no position jumps): optimized code usable only for the colorspaces supported by QImage |
1226 | for (qint32 c = 0; c < channel_num; ++c) { |
1227 | for (qint32 y = 0, h = header.height; y < h; ++y) { |
1228 | auto&& strideSize = strides.at(i: c * qsizetype(h) + y); |
1229 | if (!readChannel(target&: rawStride, stream, compressedSize: strideSize, compression)) { |
1230 | qDebug() << "Error while reading the stream of channel" << c << "line" << y; |
1231 | return false; |
1232 | } |
1233 | |
1234 | auto scanLine = img.scanLine(y); |
1235 | if (header.depth == 1) { // Bitmap |
1236 | monoInvert(target: scanLine, source: rawStride.data(), bytes: std::min(a: rawStride.size(), b: img.bytesPerLine())); |
1237 | } |
1238 | else if (header.depth == 8) { // 8-bits images: Indexed, Grayscale, RGB/RGBA |
1239 | planarToChunchy<quint8>(target: scanLine, source: rawStride.data(), width: header.width, c, cn: imgChannels); |
1240 | } |
1241 | else if (header.depth == 16) { // 16-bits integer images: Grayscale, RGB/RGBA |
1242 | planarToChunchy<quint16>(target: scanLine, source: rawStride.data(), width: header.width, c, cn: imgChannels); |
1243 | } |
1244 | else if (header.depth == 32 && header.color_mode == CM_RGB) { // 32-bits float images: RGB/RGBA |
1245 | planarToChunchy<float>(target: scanLine, source: rawStride.data(), width: header.width, c, cn: imgChannels); |
1246 | } |
1247 | else if (header.depth == 32 && header.color_mode == CM_GRAYSCALE) { // 32-bits float images: Grayscale (coverted to equivalent integer 16-bits) |
1248 | planarToChunchyFloatToUInt16<float>(target: scanLine, source: rawStride.data(), width: header.width, c, cn: imgChannels); |
1249 | } |
1250 | } |
1251 | } |
1252 | } |
1253 | |
1254 | |
1255 | // Resolution info |
1256 | if (!setResolution(img, irs)) { |
1257 | // qDebug() << "No resolution info found!"; |
1258 | } |
1259 | |
1260 | // ICC profile |
1261 | if (header.color_mode == CM_LABCOLOR) { |
1262 | // LAB conversion generates a sRGB image |
1263 | #ifdef PSD_FAST_LAB_CONVERSION |
1264 | img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear)); |
1265 | #else |
1266 | img.setColorSpace(QColorSpace(QColorSpace::SRgb)); |
1267 | #endif |
1268 | } |
1269 | else if (!setColorSpace(img, irs)) { |
1270 | // qDebug() << "No colorspace info set!"; |
1271 | } |
1272 | |
1273 | // XMP data |
1274 | if (!setXmpData(img, irs)) { |
1275 | // qDebug() << "No XMP data found!"; |
1276 | } |
1277 | |
1278 | // Duotone images: color data contains the duotone specification (not documented). |
1279 | // Other applications that read Photoshop files can treat a duotone image as a gray image, |
1280 | // and just preserve the contents of the duotone information when reading and writing the file. |
1281 | if (!cmds.duotone.data.isEmpty()) { |
1282 | img.setText(QStringLiteral("PSDDuotoneOptions" ), value: QString::fromUtf8(ba: cmds.duotone.data.toHex())); |
1283 | } |
1284 | |
1285 | return true; |
1286 | } |
1287 | |
1288 | } // Private |
1289 | |
1290 | PSDHandler::PSDHandler() |
1291 | { |
1292 | } |
1293 | |
1294 | bool PSDHandler::canRead() const |
1295 | { |
1296 | if (canRead(device: device())) { |
1297 | setFormat("psd" ); |
1298 | return true; |
1299 | } |
1300 | return false; |
1301 | } |
1302 | |
1303 | bool PSDHandler::read(QImage *image) |
1304 | { |
1305 | QDataStream s(device()); |
1306 | s.setByteOrder(QDataStream::BigEndian); |
1307 | |
1308 | PSDHeader ; |
1309 | s >> header; |
1310 | |
1311 | // Check image file format. |
1312 | if (s.atEnd() || !IsValid(header)) { |
1313 | // qDebug() << "This PSD file is not valid."; |
1314 | return false; |
1315 | } |
1316 | |
1317 | // Check if it's a supported format. |
1318 | if (!IsSupported(header)) { |
1319 | // qDebug() << "This PSD file is not supported."; |
1320 | return false; |
1321 | } |
1322 | |
1323 | QImage img; |
1324 | if (!LoadPSD(stream&: s, header, img)) { |
1325 | // qDebug() << "Error loading PSD file."; |
1326 | return false; |
1327 | } |
1328 | |
1329 | *image = img; |
1330 | return true; |
1331 | } |
1332 | |
1333 | bool PSDHandler::supportsOption(ImageOption option) const |
1334 | { |
1335 | if (option == QImageIOHandler::Size) |
1336 | return true; |
1337 | return false; |
1338 | } |
1339 | |
1340 | QVariant PSDHandler::option(ImageOption option) const |
1341 | { |
1342 | QVariant v; |
1343 | |
1344 | if (option == QImageIOHandler::Size) { |
1345 | if (auto d = device()) { |
1346 | // transactions works on both random and sequential devices |
1347 | d->startTransaction(); |
1348 | auto ba = d->read(maxlen: sizeof(PSDHeader)); |
1349 | d->rollbackTransaction(); |
1350 | |
1351 | QDataStream s(ba); |
1352 | s.setByteOrder(QDataStream::BigEndian); |
1353 | |
1354 | PSDHeader ; |
1355 | s >> header; |
1356 | |
1357 | if (s.status() == QDataStream::Ok && IsValid(header)) |
1358 | v = QVariant::fromValue(value: QSize(header.width, header.height)); |
1359 | } |
1360 | } |
1361 | |
1362 | return v; |
1363 | } |
1364 | |
1365 | bool PSDHandler::canRead(QIODevice *device) |
1366 | { |
1367 | if (!device) { |
1368 | qWarning(msg: "PSDHandler::canRead() called with no device" ); |
1369 | return false; |
1370 | } |
1371 | |
1372 | device->startTransaction(); |
1373 | |
1374 | QDataStream s(device); |
1375 | s.setByteOrder(QDataStream::BigEndian); |
1376 | |
1377 | PSDHeader ; |
1378 | s >> header; |
1379 | |
1380 | device->rollbackTransaction(); |
1381 | |
1382 | if (s.status() != QDataStream::Ok) { |
1383 | return false; |
1384 | } |
1385 | |
1386 | if (device->isSequential()) { |
1387 | if (header.color_mode == CM_CMYK || header.color_mode == CM_LABCOLOR || header.color_mode == CM_MULTICHANNEL) { |
1388 | return false; |
1389 | } |
1390 | if (header.color_mode == CM_RGB && header.channel_count > 3) { |
1391 | return false; // supposing extra channel as alpha |
1392 | } |
1393 | } |
1394 | |
1395 | return IsSupported(header); |
1396 | } |
1397 | |
1398 | QImageIOPlugin::Capabilities PSDPlugin::capabilities(QIODevice *device, const QByteArray &format) const |
1399 | { |
1400 | if (format == "psd" || format == "psb" || format == "pdd" || format == "psdt" ) { |
1401 | return Capabilities(CanRead); |
1402 | } |
1403 | if (!format.isEmpty()) { |
1404 | return {}; |
1405 | } |
1406 | if (!device->isOpen()) { |
1407 | return {}; |
1408 | } |
1409 | |
1410 | Capabilities cap; |
1411 | if (device->isReadable() && PSDHandler::canRead(device)) { |
1412 | cap |= CanRead; |
1413 | } |
1414 | return cap; |
1415 | } |
1416 | |
1417 | QImageIOHandler *PSDPlugin::create(QIODevice *device, const QByteArray &format) const |
1418 | { |
1419 | QImageIOHandler *handler = new PSDHandler; |
1420 | handler->setDevice(device); |
1421 | handler->setFormat(format); |
1422 | return handler; |
1423 | } |
1424 | |
1425 | #include "moc_psd_p.cpp" |
1426 | |