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