|
1 /* |
|
2 * kimgio import filter for MS Windows .ico files |
|
3 * |
|
4 * Distributed under the terms of the LGPL |
|
5 * Copyright (c) 2000 Malte Starostik <malte@kde.org> |
|
6 * |
|
7 */ |
|
8 |
|
9 #include "ICOHandler.h" |
|
10 |
|
11 #include <cstring> |
|
12 #include <cstdlib> |
|
13 #include <algorithm> |
|
14 #include <vector> |
|
15 |
|
16 #include <QtGui/QImage> |
|
17 #include <QtGui/QBitmap> |
|
18 #include <QtGui/QApplication> |
|
19 #include <QtCore/QVector> |
|
20 #include <QtGui/QDesktopWidget> |
|
21 |
|
22 namespace |
|
23 { |
|
24 // Global header (see http://www.daubnet.com/formats/ICO.html) |
|
25 struct IcoHeader |
|
26 { |
|
27 enum Type { Icon = 1, Cursor }; |
|
28 quint16 reserved; |
|
29 quint16 type; |
|
30 quint16 count; |
|
31 }; |
|
32 |
|
33 inline QDataStream& operator >>( QDataStream& s, IcoHeader& h ) |
|
34 { |
|
35 return s >> h.reserved >> h.type >> h.count; |
|
36 } |
|
37 |
|
38 // Based on qt_read_dib et al. from qimage.cpp |
|
39 // (c) 1992-2002 Trolltech AS. |
|
40 struct BMP_INFOHDR |
|
41 { |
|
42 static const quint32 Size = 40; |
|
43 quint32 biSize; // size of this struct |
|
44 quint32 biWidth; // pixmap width |
|
45 quint32 biHeight; // pixmap height |
|
46 quint16 biPlanes; // should be 1 |
|
47 quint16 biBitCount; // number of bits per pixel |
|
48 enum Compression { RGB = 0 }; |
|
49 quint32 biCompression; // compression method |
|
50 quint32 biSizeImage; // size of image |
|
51 quint32 biXPelsPerMeter; // horizontal resolution |
|
52 quint32 biYPelsPerMeter; // vertical resolution |
|
53 quint32 biClrUsed; // number of colors used |
|
54 quint32 biClrImportant; // number of important colors |
|
55 }; |
|
56 const quint32 BMP_INFOHDR::Size; |
|
57 |
|
58 QDataStream& operator >>( QDataStream &s, BMP_INFOHDR &bi ) |
|
59 { |
|
60 s >> bi.biSize; |
|
61 if ( bi.biSize == BMP_INFOHDR::Size ) |
|
62 { |
|
63 s >> bi.biWidth >> bi.biHeight >> bi.biPlanes >> bi.biBitCount; |
|
64 s >> bi.biCompression >> bi.biSizeImage; |
|
65 s >> bi.biXPelsPerMeter >> bi.biYPelsPerMeter; |
|
66 s >> bi.biClrUsed >> bi.biClrImportant; |
|
67 } |
|
68 return s; |
|
69 } |
|
70 |
|
71 #if 0 |
|
72 QDataStream &operator<<( QDataStream &s, const BMP_INFOHDR &bi ) |
|
73 { |
|
74 s << bi.biSize; |
|
75 s << bi.biWidth << bi.biHeight; |
|
76 s << bi.biPlanes; |
|
77 s << bi.biBitCount; |
|
78 s << bi.biCompression; |
|
79 s << bi.biSizeImage; |
|
80 s << bi.biXPelsPerMeter << bi.biYPelsPerMeter; |
|
81 s << bi.biClrUsed << bi.biClrImportant; |
|
82 return s; |
|
83 } |
|
84 #endif |
|
85 |
|
86 // Header for every icon in the file |
|
87 struct IconRec |
|
88 { |
|
89 unsigned char width; |
|
90 unsigned char height; |
|
91 quint16 colors; |
|
92 quint16 hotspotX; |
|
93 quint16 hotspotY; |
|
94 quint32 size; |
|
95 quint32 offset; |
|
96 }; |
|
97 |
|
98 inline QDataStream& operator >>( QDataStream& s, IconRec& r ) |
|
99 { |
|
100 return s >> r.width >> r.height >> r.colors |
|
101 >> r.hotspotX >> r.hotspotY >> r.size >> r.offset; |
|
102 } |
|
103 |
|
104 struct LessDifference |
|
105 { |
|
106 LessDifference( unsigned s, unsigned c ) |
|
107 : size( s ), colors( c ) {} |
|
108 |
|
109 bool operator ()( const IconRec& lhs, const IconRec& rhs ) const |
|
110 { |
|
111 // closest size match precedes everything else |
|
112 if ( std::abs( int( lhs.width - size ) ) < |
|
113 std::abs( int( rhs.width - size ) ) ) return true; |
|
114 else if ( std::abs( int( lhs.width - size ) ) > |
|
115 std::abs( int( rhs.width - size ) ) ) return false; |
|
116 else if ( colors == 0 ) |
|
117 { |
|
118 // high/true color requested |
|
119 if ( lhs.colors == 0 ) return true; |
|
120 else if ( rhs.colors == 0 ) return false; |
|
121 else return lhs.colors > rhs.colors; |
|
122 } |
|
123 else |
|
124 { |
|
125 // indexed icon requested |
|
126 if ( lhs.colors == 0 && rhs.colors == 0 ) return false; |
|
127 else if ( lhs.colors == 0 ) return false; |
|
128 else return std::abs( int( lhs.colors - colors ) ) < |
|
129 std::abs( int( rhs.colors - colors ) ); |
|
130 } |
|
131 } |
|
132 unsigned size; |
|
133 unsigned colors; |
|
134 }; |
|
135 |
|
136 bool loadFromDIB( QDataStream& stream, const IconRec& rec, QImage& icon ) |
|
137 { |
|
138 BMP_INFOHDR header; |
|
139 stream >> header; |
|
140 if ( stream.atEnd() || header.biSize != BMP_INFOHDR::Size || |
|
141 header.biSize > rec.size || |
|
142 header.biCompression != BMP_INFOHDR::RGB || |
|
143 ( header.biBitCount != 1 && header.biBitCount != 4 && |
|
144 header.biBitCount != 8 && header.biBitCount != 24 && |
|
145 header.biBitCount != 32 ) ) return false; |
|
146 |
|
147 unsigned paletteSize, paletteEntries; |
|
148 |
|
149 if (header.biBitCount > 8) |
|
150 { |
|
151 paletteEntries = 0; |
|
152 paletteSize = 0; |
|
153 } |
|
154 else |
|
155 { |
|
156 paletteSize = (1 << header.biBitCount); |
|
157 paletteEntries = paletteSize; |
|
158 if (header.biClrUsed && header.biClrUsed < paletteSize) |
|
159 paletteEntries = header.biClrUsed; |
|
160 } |
|
161 |
|
162 // Always create a 32-bit image to get the mask right |
|
163 // Note: this is safe as rec.width, rec.height are bytes |
|
164 icon = QImage( rec.width, rec.height, QImage::Format_ARGB32 ); |
|
165 if ( icon.isNull() ) return false; |
|
166 |
|
167 QVector< QRgb > colorTable( paletteSize ); |
|
168 |
|
169 colorTable.fill( QRgb( 0 ) ); |
|
170 for ( unsigned i = 0; i < paletteEntries; ++i ) |
|
171 { |
|
172 unsigned char rgb[ 4 ]; |
|
173 stream.readRawData( reinterpret_cast< char* >( &rgb ), |
|
174 sizeof( rgb ) ); |
|
175 colorTable[ i ] = qRgb( rgb[ 2 ], rgb[ 1 ], rgb[ 0 ] ); |
|
176 } |
|
177 |
|
178 unsigned bpl = ( rec.width * header.biBitCount + 31 ) / 32 * 4; |
|
179 |
|
180 unsigned char* buf = new unsigned char[ bpl ]; |
|
181 for ( unsigned y = rec.height; !stream.atEnd() && y--; ) |
|
182 { |
|
183 stream.readRawData( reinterpret_cast< char* >( buf ), bpl ); |
|
184 unsigned char* pixel = buf; |
|
185 QRgb* p = reinterpret_cast< QRgb* >( icon.scanLine( y ) ); |
|
186 switch ( header.biBitCount ) |
|
187 { |
|
188 case 1: |
|
189 for ( unsigned x = 0; x < rec.width; ++x ) |
|
190 *p++ = colorTable[ |
|
191 ( pixel[ x / 8 ] >> ( 7 - ( x & 0x07 ) ) ) & 1 ]; |
|
192 break; |
|
193 case 4: |
|
194 for ( unsigned x = 0; x < rec.width; ++x ) |
|
195 if ( x & 1 ) *p++ = colorTable[ pixel[ x / 2 ] & 0x0f ]; |
|
196 else *p++ = colorTable[ pixel[ x / 2 ] >> 4 ]; |
|
197 break; |
|
198 case 8: |
|
199 for ( unsigned x = 0; x < rec.width; ++x ) |
|
200 *p++ = colorTable[ pixel[ x ] ]; |
|
201 break; |
|
202 case 24: |
|
203 for ( unsigned x = 0; x < rec.width; ++x ) |
|
204 *p++ = qRgb( pixel[ 3 * x + 2 ], |
|
205 pixel[ 3 * x + 1 ], |
|
206 pixel[ 3 * x ] ); |
|
207 break; |
|
208 case 32: |
|
209 for ( unsigned x = 0; x < rec.width; ++x ) |
|
210 *p++ = qRgba( pixel[ 4 * x + 2 ], |
|
211 pixel[ 4 * x + 1 ], |
|
212 pixel[ 4 * x ], |
|
213 pixel[ 4 * x + 3] ); |
|
214 break; |
|
215 } |
|
216 } |
|
217 delete[] buf; |
|
218 |
|
219 if ( header.biBitCount < 32 ) |
|
220 { |
|
221 // Traditional 1-bit mask |
|
222 bpl = ( rec.width + 31 ) / 32 * 4; |
|
223 buf = new unsigned char[ bpl ]; |
|
224 for ( unsigned y = rec.height; y--; ) |
|
225 { |
|
226 stream.readRawData( reinterpret_cast< char* >( buf ), bpl ); |
|
227 QRgb* p = reinterpret_cast< QRgb* >( icon.scanLine( y ) ); |
|
228 for ( unsigned x = 0; x < rec.width; ++x, ++p ) |
|
229 if ( ( ( buf[ x / 8 ] >> ( 7 - ( x & 0x07 ) ) ) & 1 ) ) |
|
230 *p &= RGB_MASK; |
|
231 } |
|
232 delete[] buf; |
|
233 } |
|
234 return true; |
|
235 } |
|
236 } |
|
237 |
|
238 ICOHandler::ICOHandler() |
|
239 { |
|
240 } |
|
241 |
|
242 bool ICOHandler::canRead() const |
|
243 { |
|
244 return canRead(device()); |
|
245 } |
|
246 |
|
247 bool ICOHandler::read(QImage *outImage) |
|
248 { |
|
249 |
|
250 qint64 offset = device()->pos(); |
|
251 |
|
252 QDataStream stream( device() ); |
|
253 stream.setByteOrder( QDataStream::LittleEndian ); |
|
254 IcoHeader header; |
|
255 stream >> header; |
|
256 if ( stream.atEnd() || !header.count || |
|
257 ( header.type != IcoHeader::Icon && header.type != IcoHeader::Cursor) ) |
|
258 return false; |
|
259 |
|
260 unsigned requestedSize = 32; |
|
261 unsigned requestedColors = QApplication::desktop()->depth() > 8 ? 0 : QApplication::desktop()->depth(); |
|
262 int requestedIndex = -1; |
|
263 #if 0 |
|
264 if ( io->parameters() ) |
|
265 { |
|
266 QStringList params = QString(io->parameters()).split( ';', QString::SkipEmptyParts ); |
|
267 QMap< QString, QString > options; |
|
268 for ( QStringList::ConstIterator it = params.begin(); |
|
269 it != params.end(); ++it ) |
|
270 { |
|
271 QStringList tmp = (*it).split( '=', QString::SkipEmptyParts ); |
|
272 if ( tmp.count() == 2 ) options[ tmp[ 0 ] ] = tmp[ 1 ]; |
|
273 } |
|
274 if ( options[ "index" ].toUInt() ) |
|
275 requestedIndex = options[ "index" ].toUInt(); |
|
276 if ( options[ "size" ].toUInt() ) |
|
277 requestedSize = options[ "size" ].toUInt(); |
|
278 if ( options[ "colors" ].toUInt() ) |
|
279 requestedColors = options[ "colors" ].toUInt(); |
|
280 } |
|
281 #endif |
|
282 |
|
283 typedef std::vector< IconRec > IconList; |
|
284 IconList icons; |
|
285 for ( unsigned i = 0; i < header.count; ++i ) |
|
286 { |
|
287 if ( stream.atEnd() ) |
|
288 return false; |
|
289 IconRec rec; |
|
290 stream >> rec; |
|
291 icons.push_back( rec ); |
|
292 } |
|
293 IconList::const_iterator selected; |
|
294 if (requestedIndex >= 0) { |
|
295 selected = std::min( icons.begin() + requestedIndex, icons.end() ); |
|
296 } else { |
|
297 selected = std::min_element( icons.begin(), icons.end(), |
|
298 LessDifference( requestedSize, requestedColors ) ); |
|
299 } |
|
300 if ( stream.atEnd() || selected == icons.end() || |
|
301 offset + selected->offset > device()->size() ) |
|
302 return false; |
|
303 |
|
304 device()->seek( offset + selected->offset ); |
|
305 QImage icon; |
|
306 if ( loadFromDIB( stream, *selected, icon ) ) |
|
307 { |
|
308 #ifndef QT_NO_IMAGE_TEXT |
|
309 icon.setText( "X-Index", 0, QString::number( selected - icons.begin() ) ); |
|
310 if ( header.type == IcoHeader::Cursor ) |
|
311 { |
|
312 icon.setText( "X-HotspotX", 0, QString::number( selected->hotspotX ) ); |
|
313 icon.setText( "X-HotspotY", 0, QString::number( selected->hotspotY ) ); |
|
314 } |
|
315 #endif |
|
316 *outImage = icon; |
|
317 return true; |
|
318 } |
|
319 return false; |
|
320 } |
|
321 |
|
322 bool ICOHandler::write(const QImage &/*image*/) |
|
323 { |
|
324 #if 0 |
|
325 if (image.isNull()) |
|
326 return; |
|
327 |
|
328 QByteArray dibData; |
|
329 QDataStream dib(dibData, QIODevice::ReadWrite); |
|
330 dib.setByteOrder(QDataStream::LittleEndian); |
|
331 |
|
332 QImage pixels = image; |
|
333 QImage mask; |
|
334 if (io->image().hasAlphaBuffer()) |
|
335 mask = image.createAlphaMask(); |
|
336 else |
|
337 mask = image.createHeuristicMask(); |
|
338 mask.invertPixels(); |
|
339 for ( int y = 0; y < pixels.height(); ++y ) |
|
340 for ( int x = 0; x < pixels.width(); ++x ) |
|
341 if ( mask.pixel( x, y ) == 0 ) pixels.setPixel( x, y, 0 ); |
|
342 |
|
343 if (!qt_write_dib(dib, pixels)) |
|
344 return; |
|
345 |
|
346 uint hdrPos = dib.device()->at(); |
|
347 if (!qt_write_dib(dib, mask)) |
|
348 return; |
|
349 memmove(dibData.data() + hdrPos, dibData.data() + hdrPos + BMP_WIN + 8, dibData.size() - hdrPos - BMP_WIN - 8); |
|
350 dibData.resize(dibData.size() - BMP_WIN - 8); |
|
351 |
|
352 QDataStream ico(device()); |
|
353 ico.setByteOrder(QDataStream::LittleEndian); |
|
354 IcoHeader hdr; |
|
355 hdr.reserved = 0; |
|
356 hdr.type = Icon; |
|
357 hdr.count = 1; |
|
358 ico << hdr.reserved << hdr.type << hdr.count; |
|
359 IconRec rec; |
|
360 rec.width = image.width(); |
|
361 rec.height = image.height(); |
|
362 if (image.numColors() <= 16) |
|
363 rec.colors = 16; |
|
364 else if (image.depth() <= 8) |
|
365 rec.colors = 256; |
|
366 else |
|
367 rec.colors = 0; |
|
368 rec.hotspotX = 0; |
|
369 rec.hotspotY = 0; |
|
370 rec.dibSize = dibData.size(); |
|
371 ico << rec.width << rec.height << rec.colors |
|
372 << rec.hotspotX << rec.hotspotY << rec.dibSize; |
|
373 rec.dibOffset = ico.device()->at() + sizeof(rec.dibOffset); |
|
374 ico << rec.dibOffset; |
|
375 |
|
376 BMP_INFOHDR dibHeader; |
|
377 dib.device()->at(0); |
|
378 dib >> dibHeader; |
|
379 dibHeader.biHeight = image.height() << 1; |
|
380 dib.device()->at(0); |
|
381 dib << dibHeader; |
|
382 |
|
383 ico.writeRawBytes(dibData.data(), dibData.size()); |
|
384 return true; |
|
385 #endif |
|
386 return false; |
|
387 } |
|
388 |
|
389 QByteArray ICOHandler::name() const |
|
390 { |
|
391 return "ico"; |
|
392 } |
|
393 |
|
394 bool ICOHandler::canRead(QIODevice *device) |
|
395 { |
|
396 if (!device) { |
|
397 qWarning("ICOHandler::canRead() called with no device"); |
|
398 return false; |
|
399 } |
|
400 |
|
401 const qint64 oldPos = device->pos(); |
|
402 |
|
403 char head[8]; |
|
404 qint64 readBytes = device->read(head, sizeof(head)); |
|
405 const bool readOk = readBytes == sizeof(head); |
|
406 |
|
407 if (device->isSequential()) { |
|
408 while (readBytes > 0) |
|
409 device->ungetChar(head[readBytes-- - 1]); |
|
410 } else { |
|
411 device->seek(oldPos); |
|
412 } |
|
413 |
|
414 if ( !readOk ) |
|
415 return false; |
|
416 |
|
417 return head[2] == '\001' && head[3] == '\000' && // type should be 1 |
|
418 ( head[6] == 16 || head[6] == 32 || head[6] == 64 ) && // width can only be one of those |
|
419 ( head[7] == 16 || head[7] == 32 || head[7] == 64 ); // same for height |
|
420 } |
|
421 |
|
422 class ICOPlugin : public QImageIOPlugin |
|
423 { |
|
424 public: |
|
425 QStringList keys() const; |
|
426 Capabilities capabilities(QIODevice *device, const QByteArray &format) const; |
|
427 QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const; |
|
428 }; |
|
429 |
|
430 QStringList ICOPlugin::keys() const |
|
431 { |
|
432 return QStringList() << "ico" << "ICO"; |
|
433 } |
|
434 |
|
435 QImageIOPlugin::Capabilities ICOPlugin::capabilities(QIODevice *device, const QByteArray &format) const |
|
436 { |
|
437 if (format == "ico" || format == "ICO") |
|
438 return Capabilities(CanRead); |
|
439 if (!format.isEmpty()) |
|
440 return 0; |
|
441 if (!device->isOpen()) |
|
442 return 0; |
|
443 |
|
444 Capabilities cap; |
|
445 if (device->isReadable() && ICOHandler::canRead(device)) |
|
446 cap |= CanRead; |
|
447 return cap; |
|
448 } |
|
449 |
|
450 QImageIOHandler *ICOPlugin::create(QIODevice *device, const QByteArray &format) const |
|
451 { |
|
452 QImageIOHandler *handler = new ICOHandler; |
|
453 handler->setDevice(device); |
|
454 handler->setFormat(format); |
|
455 return handler; |
|
456 } |
|
457 |
|
458 Q_EXPORT_STATIC_PLUGIN(ICOPlugin) |
|
459 Q_EXPORT_PLUGIN2(qtwebico, ICOPlugin) |