KImageFormats

psd.cpp
1 /*
2  Photoshop File Format support for QImage.
3 
4  SPDX-FileCopyrightText: 2003 Ignacio CastaƱo <[email protected]>
5  SPDX-FileCopyrightText: 2015 Alex Merry <[email protected]>
6 
7  SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9 
10 /*
11  * This code is based on Thacher Ulrich PSD loading code released
12  * into the public domain. See: http://tulrich.com/geekstuff/
13  */
14 
15 /*
16  * Documentation on this file format is available at
17  * http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/
18  */
19 
20 #include "psd_p.h"
21 
22 #include "rle_p.h"
23 
24 #include <QDataStream>
25 #include <QDebug>
26 #include <QImage>
27 
28 typedef quint32 uint;
29 typedef quint16 ushort;
30 typedef quint8 uchar;
31 
32 namespace // Private.
33 {
34 enum ColorMode {
35  CM_BITMAP = 0,
36  CM_GRAYSCALE = 1,
37  CM_INDEXED = 2,
38  CM_RGB = 3,
39  CM_CMYK = 4,
40  CM_MULTICHANNEL = 7,
41  CM_DUOTONE = 8,
42  CM_LABCOLOR = 9,
43 };
44 
45 struct PSDHeader {
46  uint signature;
47  ushort version;
48  uchar reserved[6];
49  ushort channel_count;
50  uint height;
51  uint width;
52  ushort depth;
53  ushort color_mode;
54 };
55 
56 static QDataStream &operator>>(QDataStream &s, PSDHeader &header)
57 {
58  s >> header.signature;
59  s >> header.version;
60  for (int i = 0; i < 6; i++) {
61  s >> header.reserved[i];
62  }
63  s >> header.channel_count;
64  s >> header.height;
65  s >> header.width;
66  s >> header.depth;
67  s >> header.color_mode;
68  return s;
69 }
70 
71 // Check that the header is a valid PSD.
72 static bool IsValid(const PSDHeader &header)
73 {
74  if (header.signature != 0x38425053) { // '8BPS'
75  return false;
76  }
77  return true;
78 }
79 
80 // Check that the header is supported.
81 static bool IsSupported(const PSDHeader &header)
82 {
83  if (header.version != 1) {
84  return false;
85  }
86  if (header.channel_count > 16) {
87  return false;
88  }
89  if (header.depth != 8 && header.depth != 16) {
90  return false;
91  }
92  if (header.color_mode != CM_RGB) {
93  return false;
94  }
95  return true;
96 }
97 
98 static void skip_section(QDataStream &s)
99 {
100  quint32 section_length;
101  // Skip mode data.
102  s >> section_length;
103  s.skipRawData(section_length);
104 }
105 
106 template<class Trait>
107 static Trait readPixel(QDataStream &stream)
108 {
109  Trait pixel;
110  stream >> pixel;
111  return pixel;
112 }
113 
114 static QRgb updateRed(QRgb oldPixel, quint8 redPixel)
115 {
116  return qRgba(redPixel, qGreen(oldPixel), qBlue(oldPixel), qAlpha(oldPixel));
117 }
118 static QRgb updateGreen(QRgb oldPixel, quint8 greenPixel)
119 {
120  return qRgba(qRed(oldPixel), greenPixel, qBlue(oldPixel), qAlpha(oldPixel));
121 }
122 static QRgb updateBlue(QRgb oldPixel, quint8 bluePixel)
123 {
124  return qRgba(qRed(oldPixel), qGreen(oldPixel), bluePixel, qAlpha(oldPixel));
125 }
126 static QRgb updateAlpha(QRgb oldPixel, quint8 alphaPixel)
127 {
128  return qRgba(qRed(oldPixel), qGreen(oldPixel), qBlue(oldPixel), alphaPixel);
129 }
130 typedef QRgb (*channelUpdater)(QRgb, quint8);
131 
132 // Load the PSD image.
133 static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
134 {
135  // Mode data
136  skip_section(stream);
137 
138  // Image resources
139  skip_section(stream);
140 
141  // Reserved data
142  skip_section(stream);
143 
144  // Find out if the data is compressed.
145  // Known values:
146  // 0: no compression
147  // 1: RLE compressed
148  quint16 compression;
149  stream >> compression;
150 
151  if (compression > 1) {
152  qDebug() << "Unknown compression type";
153  return false;
154  }
155 
156  quint32 channel_num = header.channel_count;
157 
158  QImage::Format fmt = header.depth == 8 ? QImage::Format_RGB32 : QImage::Format_RGBX64;
159  // Clear the image.
160  if (channel_num >= 4) {
161  // Enable alpha.
162  fmt = header.depth == 8 ? QImage::Format_ARGB32 : QImage::Format_RGBA64;
163 
164  // Ignore the other channels.
165  channel_num = 4;
166  }
167 
168  img = QImage(header.width, header.height, fmt);
169  if (img.isNull()) {
170  qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height);
171  return false;
172  }
173  img.fill(qRgb(0, 0, 0));
174 
175  const quint32 pixel_count = header.height * header.width;
176  const quint32 channel_size = pixel_count * header.depth / 8;
177 
178  // Verify this, as this is used to write into the memory of the QImage
179  if (pixel_count > img.sizeInBytes() / (header.depth == 8 ? sizeof(QRgb) : sizeof(QRgba64))) {
180  qWarning() << "Invalid pixel count!" << pixel_count << "bytes available:" << img.sizeInBytes();
181  return false;
182  }
183 
184  QRgb *image_data = reinterpret_cast<QRgb *>(img.bits());
185 
186  if (!image_data) {
187  return false;
188  }
189 
190  static const channelUpdater updaters[4] = {updateRed, updateGreen, updateBlue, updateAlpha};
191 
192  typedef QRgba64 (*channelUpdater16)(QRgba64, quint16);
193  static const channelUpdater16 updaters64[4] = {[](QRgba64 oldPixel, quint16 redPixel) {
194  return qRgba64((oldPixel & ~(0xFFFFull << 0)) | (quint64(redPixel) << 0));
195  },
196  [](QRgba64 oldPixel, quint16 greenPixel) {
197  return qRgba64((oldPixel & ~(0xFFFFull << 16)) | (quint64(greenPixel) << 16));
198  },
199  [](QRgba64 oldPixel, quint16 bluePixel) {
200  return qRgba64((oldPixel & ~(0xFFFFull << 32)) | (quint64(bluePixel) << 32));
201  },
202  [](QRgba64 oldPixel, quint16 alphaPixel) {
203  return qRgba64((oldPixel & ~(0xFFFFull << 48)) | (quint64(alphaPixel) << 48));
204  }};
205 
206  if (compression) {
207  // Skip row lengths.
208  int skip_count = header.height * header.channel_count * sizeof(quint16);
209  if (stream.skipRawData(skip_count) != skip_count) {
210  return false;
211  }
212 
213  for (unsigned short channel = 0; channel < channel_num; channel++) {
214  bool success = false;
215  if (header.depth == 8) {
216  success = decodeRLEData(RLEVariant::PackBits, stream, image_data, channel_size, &readPixel<quint8>, updaters[channel]);
217  } else if (header.depth == 16) {
218  QRgba64 *image_data = reinterpret_cast<QRgba64 *>(img.bits());
219  success = decodeRLEData(RLEVariant::PackBits16, stream, image_data, channel_size, &readPixel<quint8>, updaters64[channel]);
220  }
221 
222  if (!success) {
223  qDebug() << "decodeRLEData on channel" << channel << "failed";
224  return false;
225  }
226  }
227  } else {
228  for (unsigned short channel = 0; channel < channel_num; channel++) {
229  if (header.depth == 8) {
230  for (unsigned i = 0; i < pixel_count; ++i) {
231  image_data[i] = updaters[channel](image_data[i], readPixel<quint8>(stream));
232  }
233  } else if (header.depth == 16) {
234  QRgba64 *image_data = reinterpret_cast<QRgba64 *>(img.bits());
235  for (unsigned i = 0; i < pixel_count; ++i) {
236  image_data[i] = updaters64[channel](image_data[i], readPixel<quint16>(stream));
237  }
238  }
239  // make sure we didn't try to read past the end of the stream
240  if (stream.status() != QDataStream::Ok) {
241  qDebug() << "DataStream status was" << stream.status();
242  return false;
243  }
244  }
245  }
246 
247  return true;
248 }
249 
250 } // Private
251 
252 PSDHandler::PSDHandler()
253 {
254 }
255 
256 bool PSDHandler::canRead() const
257 {
258  if (canRead(device())) {
259  setFormat("psd");
260  return true;
261  }
262  return false;
263 }
264 
265 bool PSDHandler::read(QImage *image)
266 {
267  QDataStream s(device());
269 
270  PSDHeader header;
271  s >> header;
272 
273  // Check image file format.
274  if (s.atEnd() || !IsValid(header)) {
275  // qDebug() << "This PSD file is not valid.";
276  return false;
277  }
278 
279  // Check if it's a supported format.
280  if (!IsSupported(header)) {
281  // qDebug() << "This PSD file is not supported.";
282  return false;
283  }
284 
285  QImage img;
286  if (!LoadPSD(s, header, img)) {
287  // qDebug() << "Error loading PSD file.";
288  return false;
289  }
290 
291  *image = img;
292  return true;
293 }
294 
295 bool PSDHandler::canRead(QIODevice *device)
296 {
297  if (!device) {
298  qWarning("PSDHandler::canRead() called with no device");
299  return false;
300  }
301 
302  qint64 oldPos = device->pos();
303 
304  char head[4];
305  qint64 readBytes = device->read(head, sizeof(head));
306  if (readBytes < 0) {
307  qWarning() << "Read failed" << device->errorString();
308  return false;
309  }
310 
311  if (readBytes != sizeof(head)) {
312  if (device->isSequential()) {
313  while (readBytes > 0) {
314  device->ungetChar(head[readBytes-- - 1]);
315  }
316  } else {
317  device->seek(oldPos);
318  }
319  return false;
320  }
321 
322  if (device->isSequential()) {
323  while (readBytes > 0) {
324  device->ungetChar(head[readBytes-- - 1]);
325  }
326  } else {
327  device->seek(oldPos);
328  }
329 
330  return qstrncmp(head, "8BPS", 4) == 0;
331 }
332 
333 QImageIOPlugin::Capabilities PSDPlugin::capabilities(QIODevice *device, const QByteArray &format) const
334 {
335  if (format == "psd") {
336  return Capabilities(CanRead);
337  }
338  if (!format.isEmpty()) {
339  return {};
340  }
341  if (!device->isOpen()) {
342  return {};
343  }
344 
345  Capabilities cap;
346  if (device->isReadable() && PSDHandler::canRead(device)) {
347  cap |= CanRead;
348  }
349  return cap;
350 }
351 
352 QImageIOHandler *PSDPlugin::create(QIODevice *device, const QByteArray &format) const
353 {
354  QImageIOHandler *handler = new PSDHandler;
355  handler->setDevice(device);
356  handler->setFormat(format);
357  return handler;
358 }
virtual bool seek(qint64 pos)
QString errorString() const const
QDataStream & operator>>(QDataStream &in, KDateTime::Spec &spec)
bool isEmpty() const const
bool isNull() const const
qsizetype sizeInBytes() const const
bool isReadable() const const
QDataStream::Status status() const const
virtual bool isSequential() const const
virtual qint64 pos() const const
void fill(uint pixelValue)
void setDevice(QIODevice *device)
qint64 read(char *data, qint64 maxSize)
bool isOpen() const const
bool atEnd() const const
void setByteOrder(QDataStream::ByteOrder bo)
uchar * bits()
typedef Capabilities
void setFormat(const QByteArray &format)
void ungetChar(char c)
int skipRawData(int len)
KDB_EXPORT KDbVersionInfo version()
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Thu Dec 2 2021 22:45:39 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.