KImageFormats

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

KDE's Doxygen guidelines are available online.