• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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