1 | /* This file is part of the KDE project |
2 | Copyright (C) 2003 Ignacio CastaƱo <castano@ludicon.com> |
3 | |
4 | This program is free software; you can redistribute it and/or |
5 | modify it under the terms of the Lesser GNU General Public |
6 | License as published by the Free Software Foundation; either |
7 | version 2 of the License, or (at your option) any later version. |
8 | |
9 | This code is based on Thacher Ulrich PSD loading code released |
10 | on public domain. See: http://tulrich.com/geekstuff/ |
11 | */ |
12 | |
13 | /* this code supports: |
14 | * reading: |
15 | * rle and raw psd files |
16 | * writing: |
17 | * not supported |
18 | */ |
19 | |
20 | #include "psd.h" |
21 | |
22 | #include <QtGui/QImage> |
23 | #include <QtCore/QDataStream> |
24 | |
25 | #include <kdebug.h> |
26 | |
27 | typedef quint32 uint; |
28 | typedef quint16 ushort; |
29 | typedef quint8 uchar; |
30 | |
31 | namespace { // Private. |
32 | |
33 | enum ColorMode { |
34 | CM_BITMAP = 0, |
35 | CM_GRAYSCALE = 1, |
36 | CM_INDEXED = 2, |
37 | CM_RGB = 3, |
38 | CM_CMYK = 4, |
39 | CM_MULTICHANNEL = 7, |
40 | CM_DUOTONE = 8, |
41 | CM_LABCOLOR = 9 |
42 | }; |
43 | |
44 | struct { |
45 | uint ; |
46 | ushort ; |
47 | uchar [6]; |
48 | ushort ; |
49 | uint ; |
50 | uint ; |
51 | ushort ; |
52 | ushort ; |
53 | }; |
54 | |
55 | static QDataStream & ( QDataStream & s, PSDHeader & ) |
56 | { |
57 | s >> header.signature; |
58 | s >> header.version; |
59 | for( int i = 0; i < 6; i++ ) { |
60 | s >> header.reserved[i]; |
61 | } |
62 | s >> header.channel_count; |
63 | s >> header.height; |
64 | s >> header.width; |
65 | s >> header.depth; |
66 | s >> header.color_mode; |
67 | return s; |
68 | } |
69 | static bool seekBy(QDataStream& s, unsigned int bytes) |
70 | { |
71 | char buf[4096]; |
72 | while (bytes) { |
73 | unsigned int num= qMin(bytes,( unsigned int )sizeof(buf)); |
74 | unsigned int l = num; |
75 | s.readRawData(buf, l); |
76 | if(l != num) |
77 | return false; |
78 | bytes -= num; |
79 | } |
80 | return true; |
81 | } |
82 | |
83 | // Check that the header is a valid PSD. |
84 | static bool ( const PSDHeader & ) |
85 | { |
86 | if( header.signature != 0x38425053 ) { // '8BPS' |
87 | return false; |
88 | } |
89 | return true; |
90 | } |
91 | |
92 | // Check that the header is supported. |
93 | static bool ( const PSDHeader & ) |
94 | { |
95 | if( header.version != 1 ) { |
96 | return false; |
97 | } |
98 | if( header.channel_count > 16 ) { |
99 | return false; |
100 | } |
101 | if( header.depth != 8 ) { |
102 | return false; |
103 | } |
104 | if( header.color_mode != CM_RGB ) { |
105 | return false; |
106 | } |
107 | return true; |
108 | } |
109 | |
110 | // Load the PSD image. |
111 | static bool ( QDataStream & s, const PSDHeader & , QImage & img ) |
112 | { |
113 | // Create dst image. |
114 | img = QImage( header.width, header.height, QImage::Format_RGB32 ); |
115 | |
116 | uint tmp; |
117 | |
118 | // Skip mode data. |
119 | s >> tmp; |
120 | s.device()->seek( s.device()->pos() + tmp ); |
121 | |
122 | // Skip image resources. |
123 | s >> tmp; |
124 | s.device()->seek( s.device()->pos() + tmp ); |
125 | |
126 | // Skip the reserved data. |
127 | s >> tmp; |
128 | s.device()->seek( s.device()->pos() + tmp ); |
129 | |
130 | // Find out if the data is compressed. |
131 | // Known values: |
132 | // 0: no compression |
133 | // 1: RLE compressed |
134 | ushort compression; |
135 | s >> compression; |
136 | |
137 | if( compression > 1 ) { |
138 | // Unknown compression type. |
139 | return false; |
140 | } |
141 | |
142 | uint channel_num = header.channel_count; |
143 | |
144 | // Clear the image. |
145 | if( channel_num < 4 ) { |
146 | img.fill(qRgba(0, 0, 0, 0xFF)); |
147 | } |
148 | else { |
149 | // Enable alpha. |
150 | img = img.convertToFormat(QImage::Format_ARGB32); |
151 | |
152 | // Ignore the other channels. |
153 | channel_num = 4; |
154 | } |
155 | |
156 | const uint pixel_count = header.height * header.width; |
157 | |
158 | static const uint components[4] = {2, 1, 0, 3}; // @@ Is this endian dependant? |
159 | |
160 | if( compression ) { |
161 | |
162 | // Skip row lengths. |
163 | if(!seekBy(s, header.height*header.channel_count*sizeof(ushort))) |
164 | return false; |
165 | |
166 | // Read RLE data. |
167 | for(uint channel = 0; channel < channel_num; channel++) { |
168 | |
169 | uchar * ptr = img.bits() + components[channel]; |
170 | |
171 | uint count = 0; |
172 | while( count < pixel_count ) { |
173 | uchar c; |
174 | if(s.atEnd()) |
175 | return false; |
176 | s >> c; |
177 | uint len = c; |
178 | |
179 | if( len < 128 ) { |
180 | // Copy next len+1 bytes literally. |
181 | len++; |
182 | count += len; |
183 | if ( count > pixel_count ) |
184 | return false; |
185 | |
186 | while( len != 0 ) { |
187 | s >> *ptr; |
188 | ptr += 4; |
189 | len--; |
190 | } |
191 | } |
192 | else if( len > 128 ) { |
193 | // Next -len+1 bytes in the dest are replicated from next source byte. |
194 | // (Interpret len as a negative 8-bit int.) |
195 | len ^= 0xFF; |
196 | len += 2; |
197 | count += len; |
198 | if(s.atEnd() || count > pixel_count) |
199 | return false; |
200 | uchar val; |
201 | s >> val; |
202 | while( len != 0 ) { |
203 | *ptr = val; |
204 | ptr += 4; |
205 | len--; |
206 | } |
207 | } |
208 | else if( len == 128 ) { |
209 | // No-op. |
210 | } |
211 | } |
212 | } |
213 | } |
214 | else { |
215 | // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) |
216 | // where each channel consists of an 8-bit value for each pixel in the image. |
217 | |
218 | // Read the data by channel. |
219 | for(uint channel = 0; channel < channel_num; channel++) { |
220 | |
221 | uchar * ptr = img.bits() + components[channel]; |
222 | |
223 | // Read the data. |
224 | uint count = pixel_count; |
225 | while( count != 0 ) { |
226 | s >> *ptr; |
227 | ptr += 4; |
228 | count--; |
229 | } |
230 | } |
231 | } |
232 | |
233 | return true; |
234 | } |
235 | |
236 | } // Private |
237 | |
238 | |
239 | PSDHandler::PSDHandler() |
240 | { |
241 | } |
242 | |
243 | bool PSDHandler::canRead() const |
244 | { |
245 | if (canRead(device())) { |
246 | setFormat("psd" ); |
247 | return true; |
248 | } |
249 | return false; |
250 | } |
251 | |
252 | bool PSDHandler::read(QImage *image) |
253 | { |
254 | QDataStream s( device() ); |
255 | s.setByteOrder( QDataStream::BigEndian ); |
256 | |
257 | PSDHeader ; |
258 | s >> header; |
259 | |
260 | // Check image file format. |
261 | if( s.atEnd() || !IsValid( header ) ) { |
262 | kDebug(399) << "This PSD file is not valid." ; |
263 | return false; |
264 | } |
265 | |
266 | // Check if it's a supported format. |
267 | if( !IsSupported( header ) ) { |
268 | kDebug(399) << "This PSD file is not supported." ; |
269 | return false; |
270 | } |
271 | |
272 | QImage img; |
273 | if( !LoadPSD(s, header, img) ) { |
274 | kDebug(399) << "Error loading PSD file." ; |
275 | return false; |
276 | } |
277 | |
278 | *image = img; |
279 | return true; |
280 | } |
281 | |
282 | bool PSDHandler::write(const QImage &) |
283 | { |
284 | // TODO Stub! |
285 | return false; |
286 | } |
287 | |
288 | QByteArray PSDHandler::name() const |
289 | { |
290 | return "psd" ; |
291 | } |
292 | |
293 | bool PSDHandler::canRead(QIODevice *device) |
294 | { |
295 | if (!device) { |
296 | qWarning("PSDHandler::canRead() called with no device" ); |
297 | return false; |
298 | } |
299 | |
300 | qint64 oldPos = device->pos(); |
301 | |
302 | char head[4]; |
303 | qint64 readBytes = device->read(head, sizeof(head)); |
304 | if (readBytes != sizeof(head)) { |
305 | if (device->isSequential()) { |
306 | while (readBytes > 0) |
307 | device->ungetChar(head[readBytes-- - 1]); |
308 | } else { |
309 | device->seek(oldPos); |
310 | } |
311 | return false; |
312 | } |
313 | |
314 | if (device->isSequential()) { |
315 | while (readBytes > 0) |
316 | device->ungetChar(head[readBytes-- - 1]); |
317 | } else { |
318 | device->seek(oldPos); |
319 | } |
320 | |
321 | return qstrncmp(head, "8BPS" , 4) == 0; |
322 | } |
323 | |
324 | |
325 | class PSDPlugin : public QImageIOPlugin |
326 | { |
327 | public: |
328 | QStringList keys() const; |
329 | Capabilities capabilities(QIODevice *device, const QByteArray &format) const; |
330 | QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const; |
331 | }; |
332 | |
333 | QStringList PSDPlugin::keys() const |
334 | { |
335 | return QStringList() << "psd" << "PSD" ; |
336 | } |
337 | |
338 | QImageIOPlugin::Capabilities PSDPlugin::capabilities(QIODevice *device, const QByteArray &format) const |
339 | { |
340 | if (format == "psd" || format == "PSD" ) |
341 | return Capabilities(CanRead); |
342 | if (!format.isEmpty()) |
343 | return 0; |
344 | if (!device->isOpen()) |
345 | return 0; |
346 | |
347 | Capabilities cap; |
348 | if (device->isReadable() && PSDHandler::canRead(device)) |
349 | cap |= CanRead; |
350 | return cap; |
351 | } |
352 | |
353 | QImageIOHandler *PSDPlugin::create(QIODevice *device, const QByteArray &format) const |
354 | { |
355 | QImageIOHandler *handler = new PSDHandler; |
356 | handler->setDevice(device); |
357 | handler->setFormat(format); |
358 | return handler; |
359 | } |
360 | |
361 | Q_EXPORT_STATIC_PLUGIN(PSDPlugin) |
362 | Q_EXPORT_PLUGIN2(psd, PSDPlugin) |
363 | |