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

KDE's Doxygen guidelines are available online.