mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2024-12-16 13:11:36 +03:00
ff9a281b72
Change project directory structure according to application structure. Change 'nox' configuration option to something more meaningful 'nogui'. Rename 'Icons' folder to 'icons' (similar to other folders). Partially add 'nowebui' option support. Remove QConf project file.
467 lines
14 KiB
C++
467 lines
14 KiB
C++
/*
|
|
* kimgio import filter for MS Windows .ico files
|
|
*
|
|
* Distributed under the terms of the LGPL
|
|
* Copyright (c) 2000 Malte Starostik <malte@kde.org>
|
|
*
|
|
*/
|
|
|
|
#include "ico.h"
|
|
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
#include <algorithm>
|
|
#include <vector>
|
|
|
|
#include <QImage>
|
|
#include <QBitmap>
|
|
#include <QApplication>
|
|
#include <QVector>
|
|
#include <QDesktopWidget>
|
|
|
|
namespace
|
|
{
|
|
// Global header (see http://www.daubnet.com/formats/ICO.html)
|
|
struct IcoHeader
|
|
{
|
|
enum Type { Icon = 1, Cursor };
|
|
quint16 reserved;
|
|
quint16 type;
|
|
quint16 count;
|
|
};
|
|
|
|
inline QDataStream& operator >>( QDataStream& s, IcoHeader& h )
|
|
{
|
|
return s >> h.reserved >> h.type >> h.count;
|
|
}
|
|
|
|
// Based on qt_read_dib et al. from qimage.cpp
|
|
// (c) 1992-2002 Trolltech AS.
|
|
struct BMP_INFOHDR
|
|
{
|
|
static const quint32 Size = 40;
|
|
quint32 biSize; // size of this struct
|
|
quint32 biWidth; // pixmap width
|
|
quint32 biHeight; // pixmap height
|
|
quint16 biPlanes; // should be 1
|
|
quint16 biBitCount; // number of bits per pixel
|
|
enum Compression { RGB = 0 };
|
|
quint32 biCompression; // compression method
|
|
quint32 biSizeImage; // size of image
|
|
quint32 biXPelsPerMeter; // horizontal resolution
|
|
quint32 biYPelsPerMeter; // vertical resolution
|
|
quint32 biClrUsed; // number of colors used
|
|
quint32 biClrImportant; // number of important colors
|
|
};
|
|
const quint32 BMP_INFOHDR::Size;
|
|
|
|
QDataStream& operator >>( QDataStream &s, BMP_INFOHDR &bi )
|
|
{
|
|
s >> bi.biSize;
|
|
if ( bi.biSize == BMP_INFOHDR::Size )
|
|
{
|
|
s >> bi.biWidth >> bi.biHeight >> bi.biPlanes >> bi.biBitCount;
|
|
s >> bi.biCompression >> bi.biSizeImage;
|
|
s >> bi.biXPelsPerMeter >> bi.biYPelsPerMeter;
|
|
s >> bi.biClrUsed >> bi.biClrImportant;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
#if 0
|
|
QDataStream &operator<<( QDataStream &s, const BMP_INFOHDR &bi )
|
|
{
|
|
s << bi.biSize;
|
|
s << bi.biWidth << bi.biHeight;
|
|
s << bi.biPlanes;
|
|
s << bi.biBitCount;
|
|
s << bi.biCompression;
|
|
s << bi.biSizeImage;
|
|
s << bi.biXPelsPerMeter << bi.biYPelsPerMeter;
|
|
s << bi.biClrUsed << bi.biClrImportant;
|
|
return s;
|
|
}
|
|
#endif
|
|
|
|
// Header for every icon in the file
|
|
struct IconRec
|
|
{
|
|
unsigned char width;
|
|
unsigned char height;
|
|
quint16 colors;
|
|
quint16 hotspotX;
|
|
quint16 hotspotY;
|
|
quint32 size;
|
|
quint32 offset;
|
|
};
|
|
|
|
inline QDataStream& operator >>( QDataStream& s, IconRec& r )
|
|
{
|
|
return s >> r.width >> r.height >> r.colors
|
|
>> r.hotspotX >> r.hotspotY >> r.size >> r.offset;
|
|
}
|
|
|
|
struct LessDifference
|
|
{
|
|
LessDifference( unsigned s, unsigned c )
|
|
: size( s ), colors( c ) {}
|
|
|
|
bool operator ()( const IconRec& lhs, const IconRec& rhs ) const
|
|
{
|
|
// closest size match precedes everything else
|
|
if ( std::abs( int( lhs.width - size ) ) <
|
|
std::abs( int( rhs.width - size ) ) ) return true;
|
|
else if ( std::abs( int( lhs.width - size ) ) >
|
|
std::abs( int( rhs.width - size ) ) ) return false;
|
|
else if ( colors == 0 )
|
|
{
|
|
// high/true color requested
|
|
if ( lhs.colors == 0 ) return true;
|
|
else if ( rhs.colors == 0 ) return false;
|
|
else return lhs.colors > rhs.colors;
|
|
}
|
|
else
|
|
{
|
|
// indexed icon requested
|
|
if ( lhs.colors == 0 && rhs.colors == 0 ) return false;
|
|
else if ( lhs.colors == 0 ) return false;
|
|
else return std::abs( int( lhs.colors - colors ) ) <
|
|
std::abs( int( rhs.colors - colors ) );
|
|
}
|
|
}
|
|
unsigned size;
|
|
unsigned colors;
|
|
};
|
|
|
|
bool loadFromDIB( QDataStream& stream, const IconRec& rec, QImage& icon )
|
|
{
|
|
BMP_INFOHDR header;
|
|
stream >> header;
|
|
if ( stream.atEnd() || header.biSize != BMP_INFOHDR::Size ||
|
|
header.biSize > rec.size ||
|
|
header.biCompression != BMP_INFOHDR::RGB ||
|
|
( header.biBitCount != 1 && header.biBitCount != 4 &&
|
|
header.biBitCount != 8 && header.biBitCount != 24 &&
|
|
header.biBitCount != 32 ) ) return false;
|
|
|
|
unsigned paletteSize, paletteEntries;
|
|
|
|
if (header.biBitCount > 8)
|
|
{
|
|
paletteEntries = 0;
|
|
paletteSize = 0;
|
|
}
|
|
else
|
|
{
|
|
paletteSize = (1 << header.biBitCount);
|
|
paletteEntries = paletteSize;
|
|
if (header.biClrUsed && header.biClrUsed < paletteSize)
|
|
paletteEntries = header.biClrUsed;
|
|
}
|
|
|
|
// Always create a 32-bit image to get the mask right
|
|
// Note: this is safe as rec.width, rec.height are bytes
|
|
icon = QImage( rec.width, rec.height, QImage::Format_ARGB32 );
|
|
if ( icon.isNull() ) return false;
|
|
|
|
QVector< QRgb > colorTable( paletteSize );
|
|
|
|
colorTable.fill( QRgb( 0 ) );
|
|
for ( unsigned i = 0; i < paletteEntries; ++i )
|
|
{
|
|
unsigned char rgb[ 4 ];
|
|
stream.readRawData( reinterpret_cast< char* >( &rgb ),
|
|
sizeof( rgb ) );
|
|
colorTable[ i ] = qRgb( rgb[ 2 ], rgb[ 1 ], rgb[ 0 ] );
|
|
}
|
|
|
|
unsigned bpl = ( rec.width * header.biBitCount + 31 ) / 32 * 4;
|
|
|
|
unsigned char* buf = new unsigned char[ bpl ];
|
|
for ( unsigned y = rec.height; !stream.atEnd() && y--; )
|
|
{
|
|
stream.readRawData( reinterpret_cast< char* >( buf ), bpl );
|
|
unsigned char* pixel = buf;
|
|
QRgb* p = reinterpret_cast< QRgb* >( icon.scanLine(y));
|
|
switch ( header.biBitCount )
|
|
{
|
|
case 1:
|
|
for ( unsigned x = 0; x < rec.width; ++x )
|
|
*p++ = colorTable[
|
|
( pixel[ x / 8 ] >> ( 7 - ( x & 0x07 ) ) ) & 1 ];
|
|
break;
|
|
case 4:
|
|
for ( unsigned x = 0; x < rec.width; ++x )
|
|
if ( x & 1 ) *p++ = colorTable[ pixel[ x / 2 ] & 0x0f ];
|
|
else *p++ = colorTable[ pixel[ x / 2 ] >> 4 ];
|
|
break;
|
|
case 8:
|
|
for ( unsigned x = 0; x < rec.width; ++x )
|
|
*p++ = colorTable[ pixel[ x ] ];
|
|
break;
|
|
case 24:
|
|
for ( unsigned x = 0; x < rec.width; ++x )
|
|
*p++ = qRgb( pixel[ 3 * x + 2 ],
|
|
pixel[ 3 * x + 1 ],
|
|
pixel[ 3 * x ] );
|
|
break;
|
|
case 32:
|
|
for ( unsigned x = 0; x < rec.width; ++x )
|
|
*p++ = qRgba( pixel[ 4 * x + 2 ],
|
|
pixel[ 4 * x + 1 ],
|
|
pixel[ 4 * x ],
|
|
pixel[ 4 * x + 3] );
|
|
break;
|
|
}
|
|
}
|
|
delete[] buf;
|
|
|
|
if ( header.biBitCount < 32 )
|
|
{
|
|
// Traditional 1-bit mask
|
|
bpl = ( rec.width + 31 ) / 32 * 4;
|
|
buf = new unsigned char[ bpl ];
|
|
for ( unsigned y = rec.height; y--; )
|
|
{
|
|
stream.readRawData( reinterpret_cast< char* >( buf ), bpl );
|
|
QRgb* p = reinterpret_cast< QRgb* >(icon.scanLine(y));
|
|
for ( unsigned x = 0; x < rec.width; ++x, ++p )
|
|
if ( ( ( buf[ x / 8 ] >> ( 7 - ( x & 0x07 ) ) ) & 1 ) )
|
|
*p &= RGB_MASK;
|
|
}
|
|
delete[] buf;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
ICOHandler::ICOHandler()
|
|
{
|
|
}
|
|
|
|
bool ICOHandler::canRead() const
|
|
{
|
|
if (canRead(device())) {
|
|
setFormat("ico");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ICOHandler::read(QImage *outImage)
|
|
{
|
|
|
|
qint64 offset = device()->pos();
|
|
|
|
QDataStream stream( device() );
|
|
stream.setByteOrder( QDataStream::LittleEndian );
|
|
IcoHeader header;
|
|
stream >> header;
|
|
if ( stream.atEnd() || !header.count ||
|
|
( header.type != IcoHeader::Icon && header.type != IcoHeader::Cursor) )
|
|
return false;
|
|
|
|
unsigned requestedSize = 32;
|
|
unsigned requestedColors = QApplication::desktop()->depth() > 8 ? 0 : QApplication::desktop()->depth();
|
|
int requestedIndex = -1;
|
|
#if 0
|
|
if ( io->parameters() )
|
|
{
|
|
QStringList params = QString(io->parameters()).split( ';', QString::SkipEmptyParts );
|
|
QMap< QString, QString > options;
|
|
for ( QStringList::ConstIterator it = params.begin();
|
|
it != params.end(); ++it )
|
|
{
|
|
QStringList tmp = (*it).split( '=', QString::SkipEmptyParts );
|
|
if ( tmp.count() == 2 ) options[ tmp[ 0 ] ] = tmp[ 1 ];
|
|
}
|
|
if ( options[ "index" ].toUInt() )
|
|
requestedIndex = options[ "index" ].toUInt();
|
|
if ( options[ "size" ].toUInt() )
|
|
requestedSize = options[ "size" ].toUInt();
|
|
if ( options[ "colors" ].toUInt() )
|
|
requestedColors = options[ "colors" ].toUInt();
|
|
}
|
|
#endif
|
|
|
|
typedef std::vector< IconRec > IconList;
|
|
IconList icons;
|
|
for ( unsigned i = 0; i < header.count; ++i )
|
|
{
|
|
if ( stream.atEnd() )
|
|
return false;
|
|
IconRec rec;
|
|
stream >> rec;
|
|
icons.push_back( rec );
|
|
}
|
|
IconList::const_iterator selected;
|
|
if (requestedIndex >= 0) {
|
|
selected = std::min( icons.begin() + requestedIndex, icons.end() );
|
|
} else {
|
|
selected = std::min_element( icons.begin(), icons.end(),
|
|
LessDifference( requestedSize, requestedColors ) );
|
|
}
|
|
if ( stream.atEnd() || selected == icons.end() ||
|
|
offset + selected->offset > device()->size() )
|
|
return false;
|
|
|
|
device()->seek( offset + selected->offset );
|
|
QImage icon;
|
|
if ( loadFromDIB( stream, *selected, icon ) )
|
|
{
|
|
icon.setText( "X-Index", QString::number( selected - icons.begin() ) );
|
|
if ( header.type == IcoHeader::Cursor )
|
|
{
|
|
icon.setText( "X-HotspotX", QString::number( selected->hotspotX ) );
|
|
icon.setText( "X-HotspotY", QString::number( selected->hotspotY ) );
|
|
}
|
|
|
|
*outImage = icon;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ICOHandler::write(const QImage &/*image*/)
|
|
{
|
|
#if 0
|
|
if (image.isNull())
|
|
return;
|
|
|
|
QByteArray dibData;
|
|
QDataStream dib(dibData, QIODevice::ReadWrite);
|
|
dib.setByteOrder(QDataStream::LittleEndian);
|
|
|
|
QImage pixels = image;
|
|
QImage mask;
|
|
if (io->image().hasAlphaBuffer())
|
|
mask = image.createAlphaMask();
|
|
else
|
|
mask = image.createHeuristicMask();
|
|
mask.invertPixels();
|
|
for ( int y = 0; y < pixels.height(); ++y )
|
|
for ( int x = 0; x < pixels.width(); ++x )
|
|
if ( mask.pixel( x, y ) == 0 ) pixels.setPixel( x, y, 0 );
|
|
|
|
if (!qt_write_dib(dib, pixels))
|
|
return;
|
|
|
|
uint hdrPos = dib.device()->at();
|
|
if (!qt_write_dib(dib, mask))
|
|
return;
|
|
memmove(dibData.data() + hdrPos, dibData.data() + hdrPos + BMP_WIN + 8, dibData.size() - hdrPos - BMP_WIN - 8);
|
|
dibData.resize(dibData.size() - BMP_WIN - 8);
|
|
|
|
QDataStream ico(device());
|
|
ico.setByteOrder(QDataStream::LittleEndian);
|
|
IcoHeader hdr;
|
|
hdr.reserved = 0;
|
|
hdr.type = Icon;
|
|
hdr.count = 1;
|
|
ico << hdr.reserved << hdr.type << hdr.count;
|
|
IconRec rec;
|
|
rec.width = image.width();
|
|
rec.height = image.height();
|
|
if (image.numColors() <= 16)
|
|
rec.colors = 16;
|
|
else if (image.depth() <= 8)
|
|
rec.colors = 256;
|
|
else
|
|
rec.colors = 0;
|
|
rec.hotspotX = 0;
|
|
rec.hotspotY = 0;
|
|
rec.dibSize = dibData.size();
|
|
ico << rec.width << rec.height << rec.colors
|
|
<< rec.hotspotX << rec.hotspotY << rec.dibSize;
|
|
rec.dibOffset = ico.device()->at() + sizeof(rec.dibOffset);
|
|
ico << rec.dibOffset;
|
|
|
|
BMP_INFOHDR dibHeader;
|
|
dib.device()->at(0);
|
|
dib >> dibHeader;
|
|
dibHeader.biHeight = image.height() << 1;
|
|
dib.device()->at(0);
|
|
dib << dibHeader;
|
|
|
|
ico.writeRawBytes(dibData.data(), dibData.size());
|
|
return true;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
QByteArray ICOHandler::name() const
|
|
{
|
|
return "ico";
|
|
}
|
|
|
|
bool ICOHandler::canRead(QIODevice *device)
|
|
{
|
|
if (!device) {
|
|
qWarning("ICOHandler::canRead() called with no device");
|
|
return false;
|
|
}
|
|
|
|
const qint64 oldPos = device->pos();
|
|
|
|
char head[8];
|
|
qint64 readBytes = device->read(head, sizeof(head));
|
|
const bool readOk = readBytes == sizeof(head);
|
|
|
|
if (device->isSequential()) {
|
|
while (readBytes > 0)
|
|
device->ungetChar(head[readBytes-- - 1]);
|
|
} else {
|
|
device->seek(oldPos);
|
|
}
|
|
|
|
if ( !readOk )
|
|
return false;
|
|
|
|
return head[2] == '\001' && head[3] == '\000' && // type should be 1
|
|
( head[6] == 16 || head[6] == 32 || head[6] == 64 ) && // width can only be one of those
|
|
( head[7] == 16 || head[7] == 32 || head[7] == 64 ); // same for height
|
|
}
|
|
|
|
class ICOPlugin : public QImageIOPlugin
|
|
{
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
|
|
Q_PLUGIN_METADATA(IID "org.qbittorrent.ICOPlugin")
|
|
#endif
|
|
public:
|
|
QStringList keys() const;
|
|
Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
|
|
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
|
|
};
|
|
|
|
QStringList ICOPlugin::keys() const
|
|
{
|
|
return QStringList() << "ico" << "ICO";
|
|
}
|
|
|
|
QImageIOPlugin::Capabilities ICOPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
|
{
|
|
if (format == "ico" || format == "ICO")
|
|
return Capabilities(CanRead);
|
|
if (!format.isEmpty())
|
|
return 0;
|
|
if (!device->isOpen())
|
|
return 0;
|
|
|
|
Capabilities cap;
|
|
if (device->isReadable() && ICOHandler::canRead(device))
|
|
cap |= CanRead;
|
|
return cap;
|
|
}
|
|
|
|
QImageIOHandler *ICOPlugin::create(QIODevice *device, const QByteArray &format) const
|
|
{
|
|
QImageIOHandler *handler = new ICOHandler;
|
|
handler->setDevice(device);
|
|
handler->setFormat(format);
|
|
return handler;
|
|
}
|
|
|
|
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
|
Q_EXPORT_STATIC_PLUGIN(ICOPlugin)
|
|
Q_EXPORT_PLUGIN2(ico, ICOPlugin)
|
|
#endif
|