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