2009-03-03 Adam Barth <abarth@webkit.org>
[WebKit-https.git] / WebKit / qt / Plugins / ICOHandler.cpp
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 Nokia Corporation and/or its subsidiary(-ies).
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)