00001
00002
00003
00004
00005
00006
00007
00008
00009
00010 #define QT3_SUPPORT
00011 #define QT3_SUPPORT_WARNINGS
00012 #ifdef __GNUC__
00013 #warning TODO: remove QT3_SUPPORT
00014 #endif
00015
00016 #include "ico.h"
00017
00018 #include <cstring>
00019 #include <cstdlib>
00020 #include <algorithm>
00021 #include <vector>
00022
00023 #include <QtGui/QImage>
00024 #include <QtGui/QBitmap>
00025 #include <QtGui/QApplication>
00026 #include <QtCore/QVector>
00027 #include <QtGui/QDesktopWidget>
00028
00029 namespace
00030 {
00031
00032 struct IcoHeader
00033 {
00034 enum Type { Icon = 1, Cursor };
00035 quint16 reserved;
00036 quint16 type;
00037 quint16 count;
00038 };
00039
00040 inline QDataStream& operator >>( QDataStream& s, IcoHeader& h )
00041 {
00042 return s >> h.reserved >> h.type >> h.count;
00043 }
00044
00045
00046
00047 struct BMP_INFOHDR
00048 {
00049 static const quint32 Size = 40;
00050 quint32 biSize;
00051 quint32 biWidth;
00052 quint32 biHeight;
00053 quint16 biPlanes;
00054 quint16 biBitCount;
00055 enum Compression { RGB = 0 };
00056 quint32 biCompression;
00057 quint32 biSizeImage;
00058 quint32 biXPelsPerMeter;
00059 quint32 biYPelsPerMeter;
00060 quint32 biClrUsed;
00061 quint32 biClrImportant;
00062 };
00063 const quint32 BMP_INFOHDR::Size;
00064
00065 QDataStream& operator >>( QDataStream &s, BMP_INFOHDR &bi )
00066 {
00067 s >> bi.biSize;
00068 if ( bi.biSize == BMP_INFOHDR::Size )
00069 {
00070 s >> bi.biWidth >> bi.biHeight >> bi.biPlanes >> bi.biBitCount;
00071 s >> bi.biCompression >> bi.biSizeImage;
00072 s >> bi.biXPelsPerMeter >> bi.biYPelsPerMeter;
00073 s >> bi.biClrUsed >> bi.biClrImportant;
00074 }
00075 return s;
00076 }
00077
00078 #if 0
00079 QDataStream &operator<<( QDataStream &s, const BMP_INFOHDR &bi )
00080 {
00081 s << bi.biSize;
00082 s << bi.biWidth << bi.biHeight;
00083 s << bi.biPlanes;
00084 s << bi.biBitCount;
00085 s << bi.biCompression;
00086 s << bi.biSizeImage;
00087 s << bi.biXPelsPerMeter << bi.biYPelsPerMeter;
00088 s << bi.biClrUsed << bi.biClrImportant;
00089 return s;
00090 }
00091 #endif
00092
00093
00094 struct IconRec
00095 {
00096 unsigned char width;
00097 unsigned char height;
00098 quint16 colors;
00099 quint16 hotspotX;
00100 quint16 hotspotY;
00101 quint32 size;
00102 quint32 offset;
00103 };
00104
00105 inline QDataStream& operator >>( QDataStream& s, IconRec& r )
00106 {
00107 return s >> r.width >> r.height >> r.colors
00108 >> r.hotspotX >> r.hotspotY >> r.size >> r.offset;
00109 }
00110
00111 struct LessDifference
00112 {
00113 LessDifference( unsigned s, unsigned c )
00114 : size( s ), colors( c ) {}
00115
00116 bool operator ()( const IconRec& lhs, const IconRec& rhs ) const
00117 {
00118
00119 if ( std::abs( int( lhs.width - size ) ) <
00120 std::abs( int( rhs.width - size ) ) ) return true;
00121 else if ( std::abs( int( lhs.width - size ) ) >
00122 std::abs( int( rhs.width - size ) ) ) return false;
00123 else if ( colors == 0 )
00124 {
00125
00126 if ( lhs.colors == 0 ) return true;
00127 else if ( rhs.colors == 0 ) return false;
00128 else return lhs.colors > rhs.colors;
00129 }
00130 else
00131 {
00132
00133 if ( lhs.colors == 0 && rhs.colors == 0 ) return false;
00134 else if ( lhs.colors == 0 ) return false;
00135 else return std::abs( int( lhs.colors - colors ) ) <
00136 std::abs( int( rhs.colors - colors ) );
00137 }
00138 }
00139 unsigned size;
00140 unsigned colors;
00141 };
00142
00143 bool loadFromDIB( QDataStream& stream, const IconRec& rec, QImage& icon )
00144 {
00145 BMP_INFOHDR header;
00146 stream >> header;
00147 if ( stream.atEnd() || header.biSize != BMP_INFOHDR::Size ||
00148 header.biSize > rec.size ||
00149 header.biCompression != BMP_INFOHDR::RGB ||
00150 ( header.biBitCount != 1 && header.biBitCount != 4 &&
00151 header.biBitCount != 8 && header.biBitCount != 24 &&
00152 header.biBitCount != 32 ) ) return false;
00153
00154 unsigned paletteSize, paletteEntries;
00155
00156 if (header.biBitCount > 8)
00157 {
00158 paletteEntries = 0;
00159 paletteSize = 0;
00160 }
00161 else
00162 {
00163 paletteSize = (1 << header.biBitCount);
00164 paletteEntries = paletteSize;
00165 if (header.biClrUsed && header.biClrUsed < paletteSize)
00166 paletteEntries = header.biClrUsed;
00167 }
00168
00169
00170
00171 icon = QImage( rec.width, rec.height, QImage::Format_ARGB32 );
00172 if ( icon.isNull() ) return false;
00173
00174 QVector< QRgb > colorTable( paletteSize );
00175
00176 colorTable.fill( QRgb( 0 ) );
00177 for ( unsigned i = 0; i < paletteEntries; ++i )
00178 {
00179 unsigned char rgb[ 4 ];
00180 stream.readRawData( reinterpret_cast< char* >( &rgb ),
00181 sizeof( rgb ) );
00182 colorTable[ i ] = qRgb( rgb[ 2 ], rgb[ 1 ], rgb[ 0 ] );
00183 }
00184
00185 unsigned bpl = ( rec.width * header.biBitCount + 31 ) / 32 * 4;
00186
00187 unsigned char* buf = new unsigned char[ bpl ];
00188 unsigned char** lines = icon.jumpTable();
00189 for ( unsigned y = rec.height; !stream.atEnd() && y--; )
00190 {
00191 stream.readRawData( reinterpret_cast< char* >( buf ), bpl );
00192 unsigned char* pixel = buf;
00193 QRgb* p = reinterpret_cast< QRgb* >( lines[ y ] );
00194 switch ( header.biBitCount )
00195 {
00196 case 1:
00197 for ( unsigned x = 0; x < rec.width; ++x )
00198 *p++ = colorTable[
00199 ( pixel[ x / 8 ] >> ( 7 - ( x & 0x07 ) ) ) & 1 ];
00200 break;
00201 case 4:
00202 for ( unsigned x = 0; x < rec.width; ++x )
00203 if ( x & 1 ) *p++ = colorTable[ pixel[ x / 2 ] & 0x0f ];
00204 else *p++ = colorTable[ pixel[ x / 2 ] >> 4 ];
00205 break;
00206 case 8:
00207 for ( unsigned x = 0; x < rec.width; ++x )
00208 *p++ = colorTable[ pixel[ x ] ];
00209 break;
00210 case 24:
00211 for ( unsigned x = 0; x < rec.width; ++x )
00212 *p++ = qRgb( pixel[ 3 * x + 2 ],
00213 pixel[ 3 * x + 1 ],
00214 pixel[ 3 * x ] );
00215 break;
00216 case 32:
00217 for ( unsigned x = 0; x < rec.width; ++x )
00218 *p++ = qRgba( pixel[ 4 * x + 2 ],
00219 pixel[ 4 * x + 1 ],
00220 pixel[ 4 * x ],
00221 pixel[ 4 * x + 3] );
00222 break;
00223 }
00224 }
00225 delete[] buf;
00226
00227 if ( header.biBitCount < 32 )
00228 {
00229
00230 bpl = ( rec.width + 31 ) / 32 * 4;
00231 buf = new unsigned char[ bpl ];
00232 for ( unsigned y = rec.height; y--; )
00233 {
00234 stream.readRawData( reinterpret_cast< char* >( buf ), bpl );
00235 QRgb* p = reinterpret_cast< QRgb* >( lines[ y ] );
00236 for ( unsigned x = 0; x < rec.width; ++x, ++p )
00237 if ( ( ( buf[ x / 8 ] >> ( 7 - ( x & 0x07 ) ) ) & 1 ) )
00238 *p &= RGB_MASK;
00239 }
00240 delete[] buf;
00241 }
00242 return true;
00243 }
00244 }
00245
00246 ICOHandler::ICOHandler()
00247 {
00248 }
00249
00250 bool ICOHandler::canRead() const
00251 {
00252 if (canRead(device())) {
00253 setFormat("ico");
00254 return true;
00255 }
00256 return false;
00257 }
00258
00259 bool ICOHandler::read(QImage *outImage)
00260 {
00261
00262 qint64 offset = device()->pos();
00263
00264 QDataStream stream( device() );
00265 stream.setByteOrder( QDataStream::LittleEndian );
00266 IcoHeader header;
00267 stream >> header;
00268 if ( stream.atEnd() || !header.count ||
00269 ( header.type != IcoHeader::Icon && header.type != IcoHeader::Cursor) )
00270 return false;
00271
00272 unsigned requestedSize = 32;
00273 unsigned requestedColors = QApplication::desktop()->depth() > 8 ? 0 : QApplication::desktop()->depth();
00274 int requestedIndex = -1;
00275 #if 0
00276 if ( io->parameters() )
00277 {
00278 QStringList params = QString(io->parameters()).split( ';', QString::SkipEmptyParts );
00279 QMap< QString, QString > options;
00280 for ( QStringList::ConstIterator it = params.begin();
00281 it != params.end(); ++it )
00282 {
00283 QStringList tmp = (*it).split( '=', QString::SkipEmptyParts );
00284 if ( tmp.count() == 2 ) options[ tmp[ 0 ] ] = tmp[ 1 ];
00285 }
00286 if ( options[ "index" ].toUInt() )
00287 requestedIndex = options[ "index" ].toUInt();
00288 if ( options[ "size" ].toUInt() )
00289 requestedSize = options[ "size" ].toUInt();
00290 if ( options[ "colors" ].toUInt() )
00291 requestedColors = options[ "colors" ].toUInt();
00292 }
00293 #endif
00294
00295 typedef std::vector< IconRec > IconList;
00296 IconList icons;
00297 for ( unsigned i = 0; i < header.count; ++i )
00298 {
00299 if ( stream.atEnd() )
00300 return false;
00301 IconRec rec;
00302 stream >> rec;
00303 icons.push_back( rec );
00304 }
00305 IconList::const_iterator selected;
00306 if (requestedIndex >= 0) {
00307 selected = std::min( icons.begin() + requestedIndex, icons.end() );
00308 } else {
00309 selected = std::min_element( icons.begin(), icons.end(),
00310 LessDifference( requestedSize, requestedColors ) );
00311 }
00312 if ( stream.atEnd() || selected == icons.end() ||
00313 offset + selected->offset > device()->size() )
00314 return false;
00315
00316 device()->seek( offset + selected->offset );
00317 QImage icon;
00318 if ( loadFromDIB( stream, *selected, icon ) )
00319 {
00320 icon.setText( "X-Index", 0, QString::number( selected - icons.begin() ) );
00321 if ( header.type == IcoHeader::Cursor )
00322 {
00323 icon.setText( "X-HotspotX", 0, QString::number( selected->hotspotX ) );
00324 icon.setText( "X-HotspotY", 0, QString::number( selected->hotspotY ) );
00325 }
00326
00327 *outImage = icon;
00328 return true;
00329 }
00330 return false;
00331 }
00332
00333 bool ICOHandler::write(const QImage &)
00334 {
00335 #if 0
00336 if (image.isNull())
00337 return;
00338
00339 QByteArray dibData;
00340 QDataStream dib(dibData, QIODevice::ReadWrite);
00341 dib.setByteOrder(QDataStream::LittleEndian);
00342
00343 QImage pixels = image;
00344 QImage mask;
00345 if (io->image().hasAlphaBuffer())
00346 mask = image.createAlphaMask();
00347 else
00348 mask = image.createHeuristicMask();
00349 mask.invertPixels();
00350 for ( int y = 0; y < pixels.height(); ++y )
00351 for ( int x = 0; x < pixels.width(); ++x )
00352 if ( mask.pixel( x, y ) == 0 ) pixels.setPixel( x, y, 0 );
00353
00354 if (!qt_write_dib(dib, pixels))
00355 return;
00356
00357 uint hdrPos = dib.device()->at();
00358 if (!qt_write_dib(dib, mask))
00359 return;
00360 memmove(dibData.data() + hdrPos, dibData.data() + hdrPos + BMP_WIN + 8, dibData.size() - hdrPos - BMP_WIN - 8);
00361 dibData.resize(dibData.size() - BMP_WIN - 8);
00362
00363 QDataStream ico(device());
00364 ico.setByteOrder(QDataStream::LittleEndian);
00365 IcoHeader hdr;
00366 hdr.reserved = 0;
00367 hdr.type = Icon;
00368 hdr.count = 1;
00369 ico << hdr.reserved << hdr.type << hdr.count;
00370 IconRec rec;
00371 rec.width = image.width();
00372 rec.height = image.height();
00373 if (image.numColors() <= 16)
00374 rec.colors = 16;
00375 else if (image.depth() <= 8)
00376 rec.colors = 256;
00377 else
00378 rec.colors = 0;
00379 rec.hotspotX = 0;
00380 rec.hotspotY = 0;
00381 rec.dibSize = dibData.size();
00382 ico << rec.width << rec.height << rec.colors
00383 << rec.hotspotX << rec.hotspotY << rec.dibSize;
00384 rec.dibOffset = ico.device()->at() + sizeof(rec.dibOffset);
00385 ico << rec.dibOffset;
00386
00387 BMP_INFOHDR dibHeader;
00388 dib.device()->at(0);
00389 dib >> dibHeader;
00390 dibHeader.biHeight = image.height() << 1;
00391 dib.device()->at(0);
00392 dib << dibHeader;
00393
00394 ico.writeRawBytes(dibData.data(), dibData.size());
00395 return true;
00396 #endif
00397 return false;
00398 }
00399
00400 QByteArray ICOHandler::name() const
00401 {
00402 return "ico";
00403 }
00404
00405 bool ICOHandler::canRead(QIODevice *device)
00406 {
00407 if (!device) {
00408 qWarning("ICOHandler::canRead() called with no device");
00409 return false;
00410 }
00411
00412 const qint64 oldPos = device->pos();
00413
00414 char head[8];
00415 qint64 readBytes = device->read(head, sizeof(head));
00416 const bool readOk = readBytes == sizeof(head);
00417
00418 if (device->isSequential()) {
00419 while (readBytes > 0)
00420 device->ungetChar(head[readBytes-- - 1]);
00421 } else {
00422 device->seek(oldPos);
00423 }
00424
00425 if ( !readOk )
00426 return false;
00427
00428 return head[2] == '\001' && head[3] == '\000' &&
00429 ( head[6] == 16 || head[6] == 32 || head[6] == 64 ) &&
00430 ( head[7] == 16 || head[7] == 32 || head[7] == 64 );
00431 }
00432
00433 class ICOPlugin : public QImageIOPlugin
00434 {
00435 public:
00436 QStringList keys() const;
00437 Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
00438 QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
00439 };
00440
00441 QStringList ICOPlugin::keys() const
00442 {
00443 return QStringList() << "ico" << "ICO";
00444 }
00445
00446 QImageIOPlugin::Capabilities ICOPlugin::capabilities(QIODevice *device, const QByteArray &format) const
00447 {
00448 if (format == "ico" || format == "ICO")
00449 return Capabilities(CanRead);
00450 if (!format.isEmpty())
00451 return 0;
00452 if (!device->isOpen())
00453 return 0;
00454
00455 Capabilities cap;
00456 if (device->isReadable() && ICOHandler::canRead(device))
00457 cap |= CanRead;
00458 return cap;
00459 }
00460
00461 QImageIOHandler *ICOPlugin::create(QIODevice *device, const QByteArray &format) const
00462 {
00463 QImageIOHandler *handler = new ICOHandler;
00464 handler->setDevice(device);
00465 handler->setFormat(format);
00466 return handler;
00467 }
00468
00469 Q_EXPORT_STATIC_PLUGIN(ICOPlugin)
00470 Q_EXPORT_PLUGIN2(ico, ICOPlugin)