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

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