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

KDE's Doxygen guidelines are available online.