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