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

source code of kimageformats/src/imageformats/psd.cpp