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

KDE's Doxygen guidelines are available online.