ico.cpp
00001 00002 /* 00003 * $Id: ico.cpp 392281 2005-02-22 16:29:46Z orlovich $ 00004 * kimgio import filter for MS Windows .ico files 00005 * 00006 * Distributed under the terms of the LGPL 00007 * Copyright (c) 2000 Malte Starostik <malte@kde.org> 00008 * 00009 */ 00010 00011 #include <cstring> 00012 #include <cstdlib> 00013 #include <algorithm> 00014 #include <vector> 00015 00016 #include <qimage.h> 00017 #include <qbitmap.h> 00018 #include <qapplication.h> 00019 #include <qmemarray.h> 00020 #include <qpaintdevicemetrics.h> 00021 00022 #include <kdelibs_export.h> 00023 00024 #include "ico.h" 00025 00026 namespace 00027 { 00028 // Global header 00029 struct IcoHeader 00030 { 00031 enum Type { Icon = 1, Cursor }; 00032 Q_UINT16 reserved; 00033 Q_UINT16 type; 00034 Q_UINT16 count; 00035 }; 00036 00037 inline QDataStream& operator >>( QDataStream& s, IcoHeader& h ) 00038 { 00039 return s >> h.reserved >> h.type >> h.count; 00040 } 00041 00042 // Based on qt_read_dib et al. from qimage.cpp 00043 // (c) 1992-2002 Trolltech AS. 00044 struct BMP_INFOHDR 00045 { 00046 static const Q_UINT32 Size = 40; 00047 Q_UINT32 biSize; // size of this struct 00048 Q_UINT32 biWidth; // pixmap width 00049 Q_UINT32 biHeight; // pixmap height 00050 Q_UINT16 biPlanes; // should be 1 00051 Q_UINT16 biBitCount; // number of bits per pixel 00052 enum Compression { RGB = 0 }; 00053 Q_UINT32 biCompression; // compression method 00054 Q_UINT32 biSizeImage; // size of image 00055 Q_UINT32 biXPelsPerMeter; // horizontal resolution 00056 Q_UINT32 biYPelsPerMeter; // vertical resolution 00057 Q_UINT32 biClrUsed; // number of colors used 00058 Q_UINT32 biClrImportant; // number of important colors 00059 }; 00060 const Q_UINT32 BMP_INFOHDR::Size; 00061 00062 QDataStream& operator >>( QDataStream &s, BMP_INFOHDR &bi ) 00063 { 00064 s >> bi.biSize; 00065 if ( bi.biSize == BMP_INFOHDR::Size ) 00066 { 00067 s >> bi.biWidth >> bi.biHeight >> bi.biPlanes >> bi.biBitCount; 00068 s >> bi.biCompression >> bi.biSizeImage; 00069 s >> bi.biXPelsPerMeter >> bi.biYPelsPerMeter; 00070 s >> bi.biClrUsed >> bi.biClrImportant; 00071 } 00072 return s; 00073 } 00074 00075 #if 0 00076 QDataStream &operator<<( QDataStream &s, const BMP_INFOHDR &bi ) 00077 { 00078 s << bi.biSize; 00079 s << bi.biWidth << bi.biHeight; 00080 s << bi.biPlanes; 00081 s << bi.biBitCount; 00082 s << bi.biCompression; 00083 s << bi.biSizeImage; 00084 s << bi.biXPelsPerMeter << bi.biYPelsPerMeter; 00085 s << bi.biClrUsed << bi.biClrImportant; 00086 return s; 00087 } 00088 #endif 00089 00090 // Header for every icon in the file 00091 struct IconRec 00092 { 00093 unsigned char width; 00094 unsigned char height; 00095 Q_UINT16 colors; 00096 Q_UINT16 hotspotX; 00097 Q_UINT16 hotspotY; 00098 Q_UINT32 size; 00099 Q_UINT32 offset; 00100 }; 00101 00102 inline QDataStream& operator >>( QDataStream& s, IconRec& r ) 00103 { 00104 return s >> r.width >> r.height >> r.colors 00105 >> r.hotspotX >> r.hotspotY >> r.size >> r.offset; 00106 } 00107 00108 struct LessDifference 00109 { 00110 LessDifference( unsigned s, unsigned c ) 00111 : size( s ), colors( c ) {} 00112 00113 bool operator ()( const IconRec& lhs, const IconRec& rhs ) const 00114 { 00115 // closest size match precedes everything else 00116 if ( std::abs( int( lhs.width - size ) ) < 00117 std::abs( int( rhs.width - size ) ) ) return true; 00118 else if ( std::abs( int( lhs.width - size ) ) > 00119 std::abs( int( rhs.width - size ) ) ) return false; 00120 else if ( colors == 0 ) 00121 { 00122 // high/true color requested 00123 if ( lhs.colors == 0 ) return true; 00124 else if ( rhs.colors == 0 ) return false; 00125 else return lhs.colors > rhs.colors; 00126 } 00127 else 00128 { 00129 // indexed icon requested 00130 if ( lhs.colors == 0 && rhs.colors == 0 ) return false; 00131 else if ( lhs.colors == 0 ) return false; 00132 else return std::abs( int( lhs.colors - colors ) ) < 00133 std::abs( int( rhs.colors - colors ) ); 00134 } 00135 } 00136 unsigned size; 00137 unsigned colors; 00138 }; 00139 00140 bool loadFromDIB( QDataStream& stream, const IconRec& rec, QImage& icon ) 00141 { 00142 BMP_INFOHDR header; 00143 stream >> header; 00144 if ( stream.atEnd() || header.biSize != BMP_INFOHDR::Size || 00145 header.biSize > rec.size || 00146 header.biCompression != BMP_INFOHDR::RGB || 00147 ( header.biBitCount != 1 && header.biBitCount != 4 && 00148 header.biBitCount != 8 && header.biBitCount != 24 && 00149 header.biBitCount != 32 ) ) return false; 00150 00151 unsigned paletteSize, paletteEntries; 00152 00153 if (header.biBitCount > 8) 00154 { 00155 paletteEntries = 0; 00156 paletteSize = 0; 00157 } 00158 else 00159 { 00160 paletteSize = (1 << header.biBitCount); 00161 paletteEntries = paletteSize; 00162 if (header.biClrUsed && header.biClrUsed < paletteSize) 00163 paletteEntries = header.biClrUsed; 00164 } 00165 00166 // Always create a 32-bit image to get the mask right 00167 // Note: this is safe as rec.width, rec.height are bytes 00168 icon.create( rec.width, rec.height, 32 ); 00169 if ( icon.isNull() ) return false; 00170 icon.setAlphaBuffer( true ); 00171 00172 QMemArray< QRgb > colorTable( paletteSize ); 00173 00174 colorTable.fill( QRgb( 0 ) ); 00175 for ( unsigned i = 0; i < paletteEntries; ++i ) 00176 { 00177 unsigned char rgb[ 4 ]; 00178 stream.readRawBytes( reinterpret_cast< char* >( &rgb ), 00179 sizeof( rgb ) ); 00180 colorTable[ i ] = qRgb( rgb[ 2 ], rgb[ 1 ], rgb[ 0 ] ); 00181 } 00182 00183 unsigned bpl = ( rec.width * header.biBitCount + 31 ) / 32 * 4; 00184 00185 unsigned char* buf = new unsigned char[ bpl ]; 00186 unsigned char** lines = icon.jumpTable(); 00187 for ( unsigned y = rec.height; !stream.atEnd() && y--; ) 00188 { 00189 stream.readRawBytes( reinterpret_cast< char* >( buf ), bpl ); 00190 unsigned char* pixel = buf; 00191 QRgb* p = reinterpret_cast< QRgb* >( lines[ y ] ); 00192 switch ( header.biBitCount ) 00193 { 00194 case 1: 00195 for ( unsigned x = 0; x < rec.width; ++x ) 00196 *p++ = colorTable[ 00197 ( pixel[ x / 8 ] >> ( 7 - ( x & 0x07 ) ) ) & 1 ]; 00198 break; 00199 case 4: 00200 for ( unsigned x = 0; x < rec.width; ++x ) 00201 if ( x & 1 ) *p++ = colorTable[ pixel[ x / 2 ] & 0x0f ]; 00202 else *p++ = colorTable[ pixel[ x / 2 ] >> 4 ]; 00203 break; 00204 case 8: 00205 for ( unsigned x = 0; x < rec.width; ++x ) 00206 *p++ = colorTable[ pixel[ x ] ]; 00207 break; 00208 case 24: 00209 for ( unsigned x = 0; x < rec.width; ++x ) 00210 *p++ = qRgb( pixel[ 3 * x + 2 ], 00211 pixel[ 3 * x + 1 ], 00212 pixel[ 3 * x ] ); 00213 break; 00214 case 32: 00215 for ( unsigned x = 0; x < rec.width; ++x ) 00216 *p++ = qRgba( pixel[ 4 * x + 2 ], 00217 pixel[ 4 * x + 1 ], 00218 pixel[ 4 * x ], 00219 pixel[ 4 * x + 3] ); 00220 break; 00221 } 00222 } 00223 delete[] buf; 00224 00225 if ( header.biBitCount < 32 ) 00226 { 00227 // Traditional 1-bit mask 00228 bpl = ( rec.width + 31 ) / 32 * 4; 00229 buf = new unsigned char[ bpl ]; 00230 for ( unsigned y = rec.height; y--; ) 00231 { 00232 stream.readRawBytes( reinterpret_cast< char* >( buf ), bpl ); 00233 QRgb* p = reinterpret_cast< QRgb* >( lines[ y ] ); 00234 for ( unsigned x = 0; x < rec.width; ++x, ++p ) 00235 if ( ( ( buf[ x / 8 ] >> ( 7 - ( x & 0x07 ) ) ) & 1 ) ) 00236 *p &= RGB_MASK; 00237 } 00238 delete[] buf; 00239 } 00240 return true; 00241 } 00242 } 00243 00244 extern "C" KDE_EXPORT void kimgio_ico_read( QImageIO* io ) 00245 { 00246 QIODevice::Offset offset = io->ioDevice()->at(); 00247 00248 QDataStream stream( io->ioDevice() ); 00249 stream.setByteOrder( QDataStream::LittleEndian ); 00250 IcoHeader header; 00251 stream >> header; 00252 if ( stream.atEnd() || !header.count || 00253 ( header.type != IcoHeader::Icon && header.type != IcoHeader::Cursor) ) 00254 return; 00255 00256 QPaintDeviceMetrics metrics( QApplication::desktop() ); 00257 unsigned requestedSize = 32; 00258 unsigned requestedColors = metrics.depth() > 8 ? 0 : metrics.depth(); 00259 int requestedIndex = -1; 00260 if ( io->parameters() ) 00261 { 00262 QStringList params = QStringList::split( ';', io->parameters() ); 00263 QMap< QString, QString > options; 00264 for ( QStringList::ConstIterator it = params.begin(); 00265 it != params.end(); ++it ) 00266 { 00267 QStringList tmp = QStringList::split( '=', *it ); 00268 if ( tmp.count() == 2 ) options[ tmp[ 0 ] ] = tmp[ 1 ]; 00269 } 00270 if ( options[ "index" ].toUInt() ) 00271 requestedIndex = options[ "index" ].toUInt(); 00272 if ( options[ "size" ].toUInt() ) 00273 requestedSize = options[ "size" ].toUInt(); 00274 if ( options[ "colors" ].toUInt() ) 00275 requestedColors = options[ "colors" ].toUInt(); 00276 } 00277 00278 typedef std::vector< IconRec > IconList; 00279 IconList icons; 00280 for ( unsigned i = 0; i < header.count; ++i ) 00281 { 00282 if ( stream.atEnd() ) return; 00283 IconRec rec; 00284 stream >> rec; 00285 icons.push_back( rec ); 00286 } 00287 IconList::const_iterator selected; 00288 if (requestedIndex >= 0) { 00289 selected = std::min( icons.begin() + requestedIndex, icons.end() ); 00290 } else { 00291 selected = std::min_element( icons.begin(), icons.end(), 00292 LessDifference( requestedSize, requestedColors ) ); 00293 } 00294 if ( stream.atEnd() || selected == icons.end() || 00295 offset + selected->offset > io->ioDevice()->size() ) 00296 return; 00297 00298 io->ioDevice()->at( offset + selected->offset ); 00299 QImage icon; 00300 if ( loadFromDIB( stream, *selected, icon ) ) 00301 { 00302 icon.setText( "X-Index", 0, QString::number( selected - icons.begin() ) ); 00303 if ( header.type == IcoHeader::Cursor ) 00304 { 00305 icon.setText( "X-HotspotX", 0, QString::number( selected->hotspotX ) ); 00306 icon.setText( "X-HotspotY", 0, QString::number( selected->hotspotY ) ); 00307 } 00308 io->setImage(icon); 00309 io->setStatus(0); 00310 } 00311 } 00312 00313 #if 0 00314 void kimgio_ico_write(QImageIO *io) 00315 { 00316 if (io->image().isNull()) 00317 return; 00318 00319 QByteArray dibData; 00320 QDataStream dib(dibData, IO_ReadWrite); 00321 dib.setByteOrder(QDataStream::LittleEndian); 00322 00323 QImage pixels = io->image(); 00324 QImage mask; 00325 if (io->image().hasAlphaBuffer()) 00326 mask = io->image().createAlphaMask(); 00327 else 00328 mask = io->image().createHeuristicMask(); 00329 mask.invertPixels(); 00330 for ( int y = 0; y < pixels.height(); ++y ) 00331 for ( int x = 0; x < pixels.width(); ++x ) 00332 if ( mask.pixel( x, y ) == 0 ) pixels.setPixel( x, y, 0 ); 00333 00334 if (!qt_write_dib(dib, pixels)) 00335 return; 00336 00337 uint hdrPos = dib.device()->at(); 00338 if (!qt_write_dib(dib, mask)) 00339 return; 00340 memmove(dibData.data() + hdrPos, dibData.data() + hdrPos + BMP_WIN + 8, dibData.size() - hdrPos - BMP_WIN - 8); 00341 dibData.resize(dibData.size() - BMP_WIN - 8); 00342 00343 QDataStream ico(io->ioDevice()); 00344 ico.setByteOrder(QDataStream::LittleEndian); 00345 IcoHeader hdr; 00346 hdr.reserved = 0; 00347 hdr.type = Icon; 00348 hdr.count = 1; 00349 ico << hdr.reserved << hdr.type << hdr.count; 00350 IconRec rec; 00351 rec.width = io->image().width(); 00352 rec.height = io->image().height(); 00353 if (io->image().numColors() <= 16) 00354 rec.colors = 16; 00355 else if (io->image().depth() <= 8) 00356 rec.colors = 256; 00357 else 00358 rec.colors = 0; 00359 rec.hotspotX = 0; 00360 rec.hotspotY = 0; 00361 rec.dibSize = dibData.size(); 00362 ico << rec.width << rec.height << rec.colors 00363 << rec.hotspotX << rec.hotspotY << rec.dibSize; 00364 rec.dibOffset = ico.device()->at() + sizeof(rec.dibOffset); 00365 ico << rec.dibOffset; 00366 00367 BMP_INFOHDR dibHeader; 00368 dib.device()->at(0); 00369 dib >> dibHeader; 00370 dibHeader.biHeight = io->image().height() << 1; 00371 dib.device()->at(0); 00372 dib << dibHeader; 00373 00374 ico.writeRawBytes(dibData.data(), dibData.size()); 00375 io->setStatus(0); 00376 } 00377 #endif