As a result of the fix for QTBUG-75214, Qt inadvertently no longer reads the AND mask that specifies transparency for 16-bit and 24-bit ico files. This is because it tries to detect 32-bit icons by checking icoAttrib.depth == 32, but icoAttrib.depth is set to the depth of the QImage, not the depth of the icon, and 32-bit QImage is used for all of the non-indexed cases (16-bit, 24-bit and 32-bit.) This commit instead uses icoAttrib.nbits, which should reliably determine whether or not the icon is 32-bit. This makes the behavior consistent with other ico reading software, including Windows. Also, adds a unit test that verifies correct behavior of icon masks, checking for both QTBUG-75214 and QTBUG-113319. Amends 1d128ed1dfbcf49453ada922e54381c37264fde5. Fixes: QTBUG-113319 Change-Id: I89ac86ff16054c8925fff6afc8c530fa737f8385 Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io> (cherry picked from commit 1079b537396fcf5cd6c50cc8af19b078a50cf547) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
818 lines
25 KiB
C++
818 lines
25 KiB
C++
// Copyright (C) 2022 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
|
|
|
/*!
|
|
\class QtIcoHandler
|
|
\since 4.4
|
|
\brief The QtIcoHandler class provides support for the ICO image format.
|
|
\internal
|
|
*/
|
|
|
|
|
|
|
|
#include "qicohandler.h"
|
|
#include <QtCore/qendian.h>
|
|
#include <private/qendian_p.h>
|
|
#include <QtGui/QImage>
|
|
#include <QtCore/QBuffer>
|
|
#include <QtCore/QFile>
|
|
#include <QtCore/QLoggingCategory>
|
|
#include <qvariant.h>
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
Q_LOGGING_CATEGORY(lcIco, "qt.gui.imageio.ico")
|
|
|
|
namespace {
|
|
|
|
// These next two structs represent how the icon information is stored
|
|
// in an ICO file.
|
|
typedef struct
|
|
{
|
|
quint8 bWidth; // Width of the image
|
|
quint8 bHeight; // Height of the image (actual height, not times 2)
|
|
quint8 bColorCount; // Number of colors in image (0 if >=8bpp) [ not ture ]
|
|
quint8 bReserved; // Reserved
|
|
quint16_le wPlanes; // Color Planes
|
|
quint16_le wBitCount; // Bits per pixel
|
|
quint32_le dwBytesInRes; // how many bytes in this resource?
|
|
quint32_le dwImageOffset; // where in the file is this image
|
|
} ICONDIRENTRY, *LPICONDIRENTRY;
|
|
#define ICONDIRENTRY_SIZE 16
|
|
|
|
typedef struct
|
|
{
|
|
quint16_le idReserved; // Reserved
|
|
quint16_le idType; // resource type (1 for icons, 2 for cursors)
|
|
quint16_le idCount; // how many images?
|
|
ICONDIRENTRY idEntries[1]; // the entries for each image
|
|
} ICONDIR, *LPICONDIR;
|
|
#define ICONDIR_SIZE 6 // Exclude the idEntries field
|
|
|
|
typedef struct { // BMP information header
|
|
quint32_le biSize; // size of this struct
|
|
quint32_le biWidth; // pixmap width
|
|
quint32_le biHeight; // pixmap height (specifies the combined height of the XOR and AND masks)
|
|
quint16_le biPlanes; // should be 1
|
|
quint16_le biBitCount; // number of bits per pixel
|
|
quint32_le biCompression; // compression method
|
|
quint32_le biSizeImage; // size of image
|
|
quint32_le biXPelsPerMeter; // horizontal resolution
|
|
quint32_le biYPelsPerMeter; // vertical resolution
|
|
quint32_le biClrUsed; // number of colors used
|
|
quint32_le biClrImportant; // number of important colors
|
|
} BMP_INFOHDR ,*LPBMP_INFOHDR;
|
|
#define BMP_INFOHDR_SIZE 40
|
|
|
|
}
|
|
|
|
class ICOReader
|
|
{
|
|
public:
|
|
ICOReader(QIODevice * iodevice);
|
|
int count();
|
|
QImage iconAt(int index);
|
|
static bool canRead(QIODevice *iodev);
|
|
|
|
static QList<QImage> read(QIODevice *device);
|
|
|
|
static bool write(QIODevice *device, const QList<QImage> &images);
|
|
|
|
bool readIconEntry(int index, ICONDIRENTRY * iconEntry);
|
|
|
|
private:
|
|
bool readHeader();
|
|
|
|
bool readBMPHeader(quint32 imageOffset, BMP_INFOHDR * header);
|
|
void findColorInfo(QImage & image);
|
|
void readColorTable(QImage & image);
|
|
|
|
void readBMP(QImage & image);
|
|
void read1BitBMP(QImage & image);
|
|
void read4BitBMP(QImage & image);
|
|
void read8BitBMP(QImage & image);
|
|
void read16_24_32BMP(QImage & image);
|
|
|
|
struct IcoAttrib
|
|
{
|
|
int nbits;
|
|
int ncolors;
|
|
int h;
|
|
int w;
|
|
int depth;
|
|
} icoAttrib;
|
|
|
|
QIODevice * iod;
|
|
qint64 startpos;
|
|
bool headerRead;
|
|
ICONDIR iconDir;
|
|
|
|
};
|
|
|
|
// Data readers and writers that takes care of alignment and endian stuff.
|
|
static bool readIconDirEntry(QIODevice *iodev, ICONDIRENTRY *iconDirEntry)
|
|
{
|
|
if (iodev)
|
|
return (iodev->read((char*)iconDirEntry, ICONDIRENTRY_SIZE) == ICONDIRENTRY_SIZE);
|
|
return false;
|
|
}
|
|
|
|
static bool writeIconDirEntry(QIODevice *iodev, const ICONDIRENTRY &iconEntry)
|
|
{
|
|
if (iodev)
|
|
return iodev->write((char*)&iconEntry, ICONDIRENTRY_SIZE) == ICONDIRENTRY_SIZE;
|
|
return false;
|
|
}
|
|
|
|
static bool readIconDir(QIODevice *iodev, ICONDIR *iconDir)
|
|
{
|
|
if (iodev)
|
|
return (iodev->read((char*)iconDir, ICONDIR_SIZE) == ICONDIR_SIZE);
|
|
return false;
|
|
}
|
|
|
|
static bool writeIconDir(QIODevice *iodev, const ICONDIR &iconDir)
|
|
{
|
|
if (iodev)
|
|
return iodev->write((char*)&iconDir, 6) == 6;
|
|
return false;
|
|
}
|
|
|
|
static bool readBMPInfoHeader(QIODevice *iodev, BMP_INFOHDR *pHeader)
|
|
{
|
|
if (iodev)
|
|
return (iodev->read((char*)pHeader, BMP_INFOHDR_SIZE) == BMP_INFOHDR_SIZE);
|
|
return false;
|
|
}
|
|
|
|
static bool writeBMPInfoHeader(QIODevice *iodev, const BMP_INFOHDR &header)
|
|
{
|
|
if (iodev)
|
|
return iodev->write((char*)&header, BMP_INFOHDR_SIZE) == BMP_INFOHDR_SIZE;
|
|
return false;
|
|
}
|
|
|
|
|
|
ICOReader::ICOReader(QIODevice * iodevice)
|
|
: iod(iodevice)
|
|
, startpos(0)
|
|
, headerRead(false)
|
|
{
|
|
}
|
|
|
|
|
|
int ICOReader::count()
|
|
{
|
|
if (readHeader())
|
|
return iconDir.idCount;
|
|
return 0;
|
|
}
|
|
|
|
bool ICOReader::canRead(QIODevice *iodev)
|
|
{
|
|
bool isProbablyICO = false;
|
|
if (iodev) {
|
|
qint64 oldPos = iodev->pos();
|
|
|
|
ICONDIR ikonDir;
|
|
if (readIconDir(iodev, &ikonDir)) {
|
|
if (readIconDirEntry(iodev, &ikonDir.idEntries[0])) {
|
|
// ICO format does not have a magic identifier, so we read 6 different values, which will hopefully be enough to identify the file.
|
|
if ( ikonDir.idReserved == 0
|
|
&& (ikonDir.idType == 1 || ikonDir.idType == 2)
|
|
&& ikonDir.idEntries[0].bReserved == 0
|
|
&& (ikonDir.idEntries[0].wPlanes <= 1 || ikonDir.idType == 2)
|
|
&& (ikonDir.idEntries[0].wBitCount <= 32 || ikonDir.idType == 2) // Bits per pixel
|
|
&& ikonDir.idEntries[0].dwBytesInRes >= 40 // Must be over 40, since sizeof (infoheader) == 40
|
|
) {
|
|
isProbablyICO = true;
|
|
}
|
|
|
|
if (iodev->isSequential()) {
|
|
// Our structs might be padded due to alignment, so we need to fetch each member before we ungetChar() !
|
|
quint32 tmp = ikonDir.idEntries[0].dwImageOffset;
|
|
iodev->ungetChar((tmp >> 24) & 0xff);
|
|
iodev->ungetChar((tmp >> 16) & 0xff);
|
|
iodev->ungetChar((tmp >> 8) & 0xff);
|
|
iodev->ungetChar(tmp & 0xff);
|
|
|
|
tmp = ikonDir.idEntries[0].dwBytesInRes;
|
|
iodev->ungetChar((tmp >> 24) & 0xff);
|
|
iodev->ungetChar((tmp >> 16) & 0xff);
|
|
iodev->ungetChar((tmp >> 8) & 0xff);
|
|
iodev->ungetChar(tmp & 0xff);
|
|
|
|
tmp = ikonDir.idEntries[0].wBitCount;
|
|
iodev->ungetChar((tmp >> 8) & 0xff);
|
|
iodev->ungetChar(tmp & 0xff);
|
|
|
|
tmp = ikonDir.idEntries[0].wPlanes;
|
|
iodev->ungetChar((tmp >> 8) & 0xff);
|
|
iodev->ungetChar(tmp & 0xff);
|
|
|
|
iodev->ungetChar(ikonDir.idEntries[0].bReserved);
|
|
iodev->ungetChar(ikonDir.idEntries[0].bColorCount);
|
|
iodev->ungetChar(ikonDir.idEntries[0].bHeight);
|
|
iodev->ungetChar(ikonDir.idEntries[0].bWidth);
|
|
}
|
|
}
|
|
|
|
if (iodev->isSequential()) {
|
|
// Our structs might be padded due to alignment, so we need to fetch each member before we ungetChar() !
|
|
quint32 tmp = ikonDir.idCount;
|
|
iodev->ungetChar((tmp >> 8) & 0xff);
|
|
iodev->ungetChar(tmp & 0xff);
|
|
|
|
tmp = ikonDir.idType;
|
|
iodev->ungetChar((tmp >> 8) & 0xff);
|
|
iodev->ungetChar(tmp & 0xff);
|
|
|
|
tmp = ikonDir.idReserved;
|
|
iodev->ungetChar((tmp >> 8) & 0xff);
|
|
iodev->ungetChar(tmp & 0xff);
|
|
}
|
|
}
|
|
if (!iodev->isSequential()) iodev->seek(oldPos);
|
|
}
|
|
|
|
return isProbablyICO;
|
|
}
|
|
|
|
bool ICOReader::readHeader()
|
|
{
|
|
if (iod && !headerRead) {
|
|
startpos = iod->pos();
|
|
if (readIconDir(iod, &iconDir)) {
|
|
if (iconDir.idReserved == 0 && (iconDir.idType == 1 || iconDir.idType == 2))
|
|
headerRead = true;
|
|
}
|
|
}
|
|
|
|
return headerRead;
|
|
}
|
|
|
|
bool ICOReader::readIconEntry(int index, ICONDIRENTRY *iconEntry)
|
|
{
|
|
if (readHeader()) {
|
|
if (iod->seek(startpos + ICONDIR_SIZE + (index * ICONDIRENTRY_SIZE))) {
|
|
return readIconDirEntry(iod, iconEntry);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
bool ICOReader::readBMPHeader(quint32 imageOffset, BMP_INFOHDR * header)
|
|
{
|
|
if (iod) {
|
|
if (iod->seek(startpos + imageOffset)) {
|
|
if (readBMPInfoHeader(iod, header)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ICOReader::findColorInfo(QImage & image)
|
|
{
|
|
if (icoAttrib.ncolors > 0) { // set color table
|
|
readColorTable(image);
|
|
} else if (icoAttrib.nbits == 16) { // don't support RGB values for 15/16 bpp
|
|
image = QImage();
|
|
}
|
|
}
|
|
|
|
void ICOReader::readColorTable(QImage & image)
|
|
{
|
|
if (iod) {
|
|
image.setColorCount(icoAttrib.ncolors);
|
|
uchar rgb[4];
|
|
for (int i=0; i<icoAttrib.ncolors; i++) {
|
|
if (iod->read((char*)rgb, 4) != 4) {
|
|
image = QImage();
|
|
break;
|
|
}
|
|
image.setColor(i, qRgb(rgb[2],rgb[1],rgb[0]));
|
|
}
|
|
} else {
|
|
image = QImage();
|
|
}
|
|
}
|
|
|
|
void ICOReader::readBMP(QImage & image)
|
|
{
|
|
if (icoAttrib.nbits == 1) { // 1 bit BMP image
|
|
read1BitBMP(image);
|
|
} else if (icoAttrib.nbits == 4) { // 4 bit BMP image
|
|
read4BitBMP(image);
|
|
} else if (icoAttrib.nbits == 8) {
|
|
read8BitBMP(image);
|
|
} else if (icoAttrib.nbits == 16 || icoAttrib.nbits == 24 || icoAttrib.nbits == 32 ) { // 16,24,32 bit BMP image
|
|
read16_24_32BMP(image);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* NOTE: A 1 bit BMP is only flipped vertically, and not horizontally like all other color depths!
|
|
* (This is the same with the bitmask)
|
|
*
|
|
*/
|
|
void ICOReader::read1BitBMP(QImage & image)
|
|
{
|
|
if (iod) {
|
|
|
|
int h = image.height();
|
|
qsizetype bpl = image.bytesPerLine();
|
|
|
|
while (--h >= 0) {
|
|
if (iod->read((char*)image.scanLine(h),bpl) != bpl) {
|
|
image = QImage();
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
image = QImage();
|
|
}
|
|
}
|
|
|
|
void ICOReader::read4BitBMP(QImage & image)
|
|
{
|
|
if (iod) {
|
|
|
|
int h = icoAttrib.h;
|
|
int buflen = ((icoAttrib.w+7)/8)*4;
|
|
uchar *buf = new uchar[buflen];
|
|
Q_CHECK_PTR(buf);
|
|
|
|
while (--h >= 0) {
|
|
if (iod->read((char*)buf,buflen) != buflen) {
|
|
image = QImage();
|
|
break;
|
|
}
|
|
uchar *p = image.scanLine(h);
|
|
uchar *b = buf;
|
|
for (int i=0; i<icoAttrib.w/2; i++) { // convert nibbles to bytes
|
|
*p++ = *b >> 4;
|
|
*p++ = *b++ & 0x0f;
|
|
}
|
|
if (icoAttrib.w & 1) // the last nibble
|
|
*p = *b >> 4;
|
|
}
|
|
|
|
delete [] buf;
|
|
|
|
} else {
|
|
image = QImage();
|
|
}
|
|
}
|
|
|
|
void ICOReader::read8BitBMP(QImage & image)
|
|
{
|
|
if (iod) {
|
|
|
|
int h = icoAttrib.h;
|
|
qsizetype bpl = image.bytesPerLine();
|
|
|
|
while (--h >= 0) {
|
|
if (iod->read((char *)image.scanLine(h), bpl) != bpl) {
|
|
image = QImage();
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
image = QImage();
|
|
}
|
|
}
|
|
|
|
void ICOReader::read16_24_32BMP(QImage & image)
|
|
{
|
|
if (iod) {
|
|
int h = icoAttrib.h;
|
|
QRgb *p;
|
|
QRgb *end;
|
|
uchar *buf = new uchar[image.bytesPerLine()];
|
|
qsizetype bpl = ((qsizetype(icoAttrib.w)*icoAttrib.nbits+31)/32)*4;
|
|
uchar *b;
|
|
|
|
while (--h >= 0) {
|
|
p = (QRgb *)image.scanLine(h);
|
|
end = p + icoAttrib.w;
|
|
if (iod->read((char *)buf, bpl) != bpl) {
|
|
image = QImage();
|
|
break;
|
|
}
|
|
b = buf;
|
|
while (p < end) {
|
|
if (icoAttrib.nbits == 24)
|
|
*p++ = qRgb(*(b+2), *(b+1), *b);
|
|
else if (icoAttrib.nbits == 32)
|
|
*p++ = qRgba(*(b+2), *(b+1), *b, *(b+3));
|
|
b += icoAttrib.nbits/8;
|
|
}
|
|
}
|
|
|
|
delete[] buf;
|
|
|
|
} else {
|
|
image = QImage();
|
|
}
|
|
}
|
|
|
|
static const char icoOrigDepthKey[] = "_q_icoOrigDepth";
|
|
|
|
QImage ICOReader::iconAt(int index)
|
|
{
|
|
QImage img;
|
|
|
|
if (count() > index) { // forces header to be read
|
|
|
|
ICONDIRENTRY iconEntry;
|
|
if (readIconEntry(index, &iconEntry)) {
|
|
|
|
static const uchar pngMagicData[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
|
|
|
|
if (!iod->seek(iconEntry.dwImageOffset)
|
|
|| iconEntry.dwBytesInRes > iod->bytesAvailable())
|
|
return img;
|
|
|
|
const QByteArray pngMagic = QByteArray::fromRawData((const char*)pngMagicData, sizeof(pngMagicData));
|
|
const bool isPngImage = (iod->read(pngMagic.size()) == pngMagic);
|
|
|
|
if (isPngImage) {
|
|
iod->seek(iconEntry.dwImageOffset);
|
|
QImage image = QImage::fromData(iod->read(iconEntry.dwBytesInRes), "png");
|
|
image.setText(QLatin1String(icoOrigDepthKey), QString::number(iconEntry.wBitCount));
|
|
return image;
|
|
}
|
|
|
|
BMP_INFOHDR header;
|
|
if (readBMPHeader(iconEntry.dwImageOffset, &header)) {
|
|
icoAttrib.nbits = header.biBitCount ? header.biBitCount : iconEntry.wBitCount;
|
|
|
|
switch (icoAttrib.nbits) {
|
|
case 32:
|
|
case 24:
|
|
case 16:
|
|
icoAttrib.depth = 32;
|
|
break;
|
|
case 8:
|
|
case 4:
|
|
icoAttrib.depth = 8;
|
|
break;
|
|
case 1:
|
|
icoAttrib.depth = 1;
|
|
break;
|
|
default:
|
|
return img;
|
|
break;
|
|
}
|
|
if (icoAttrib.depth == 32) // there's no colormap
|
|
icoAttrib.ncolors = 0;
|
|
else // # colors used
|
|
icoAttrib.ncolors = header.biClrUsed ? uint(header.biClrUsed) : 1 << icoAttrib.nbits;
|
|
if (icoAttrib.ncolors > 256) //color table can't be more than 256
|
|
return img;
|
|
icoAttrib.w = iconEntry.bWidth;
|
|
if (icoAttrib.w == 0) // means 256 pixels
|
|
icoAttrib.w = header.biWidth;
|
|
icoAttrib.h = iconEntry.bHeight;
|
|
if (icoAttrib.h == 0) // means 256 pixels
|
|
icoAttrib.h = header.biHeight/2;
|
|
if (icoAttrib.w > 256 || icoAttrib.h > 256) // Max ico size
|
|
return img;
|
|
|
|
QImage::Format format = QImage::Format_ARGB32;
|
|
if (icoAttrib.nbits == 24)
|
|
format = QImage::Format_RGB32;
|
|
else if (icoAttrib.ncolors == 2 && icoAttrib.depth == 1)
|
|
format = QImage::Format_Mono;
|
|
else if (icoAttrib.ncolors > 0)
|
|
format = QImage::Format_Indexed8;
|
|
|
|
QImage image;
|
|
const QSize size(icoAttrib.w, icoAttrib.h);
|
|
if (QImageIOHandler::allocateImage(size, format, &image)) {
|
|
findColorInfo(image);
|
|
if (!image.isNull()) {
|
|
readBMP(image);
|
|
if (!image.isNull()) {
|
|
if (icoAttrib.nbits == 32) {
|
|
img = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
|
} else {
|
|
QImage mask(image.width(), image.height(), QImage::Format_Mono);
|
|
if (!mask.isNull()) {
|
|
mask.setColorCount(2);
|
|
mask.setColor(0, qRgba(255,255,255,0xff));
|
|
mask.setColor(1, qRgba(0 ,0 ,0 ,0xff));
|
|
read1BitBMP(mask);
|
|
if (!mask.isNull()) {
|
|
img = image;
|
|
img.setAlphaChannel(mask);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
img.setText(QLatin1String(icoOrigDepthKey), QString::number(iconEntry.wBitCount));
|
|
}
|
|
}
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
|
|
/*!
|
|
Reads all the icons from the given \a device, and returns them as
|
|
a list of QImage objects.
|
|
|
|
Each image has an alpha channel that represents the mask from the
|
|
corresponding icon.
|
|
|
|
\sa write()
|
|
*/
|
|
QList<QImage> ICOReader::read(QIODevice *device)
|
|
{
|
|
QList<QImage> images;
|
|
|
|
ICOReader reader(device);
|
|
const int N = reader.count();
|
|
images.reserve(N);
|
|
for (int i = 0; i < N; i++)
|
|
images += reader.iconAt(i);
|
|
|
|
return images;
|
|
}
|
|
|
|
|
|
/*!
|
|
Writes all the QImages in the \a images list to the given \a
|
|
device. Returns \c true if the images are written successfully;
|
|
otherwise returns \c false.
|
|
|
|
The first image in the list is stored as the first icon in the
|
|
device, and is therefore used as the default icon by applications.
|
|
The alpha channel of each image is converted to a mask for each
|
|
corresponding icon.
|
|
|
|
\sa read()
|
|
*/
|
|
bool ICOReader::write(QIODevice *device, const QList<QImage> &images)
|
|
{
|
|
bool retValue = false;
|
|
|
|
if (images.size()) {
|
|
|
|
qint64 origOffset = device->pos();
|
|
|
|
ICONDIR id;
|
|
id.idReserved = 0;
|
|
id.idType = 1;
|
|
id.idCount = images.size();
|
|
|
|
ICONDIRENTRY * entries = new ICONDIRENTRY[id.idCount];
|
|
BMP_INFOHDR * bmpHeaders = new BMP_INFOHDR[id.idCount];
|
|
QByteArray * imageData = new QByteArray[id.idCount];
|
|
|
|
for (int i=0; i<id.idCount; i++) {
|
|
|
|
QImage image = images[i];
|
|
// Scale down the image if it is larger than 256 pixels in either width or height
|
|
// because this is a maximum size of image in the ICO file.
|
|
if (image.width() > 256 || image.height() > 256)
|
|
{
|
|
image = image.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
}
|
|
QImage maskImage(image.width(), image.height(), QImage::Format_Mono);
|
|
image = image.convertToFormat(QImage::Format_ARGB32);
|
|
maskImage.fill(Qt::color1);
|
|
|
|
int nbits = 32;
|
|
int bpl_bmp = ((image.width()*nbits+31)/32)*4;
|
|
|
|
entries[i].bColorCount = 0;
|
|
entries[i].bReserved = 0;
|
|
entries[i].wBitCount = nbits;
|
|
entries[i].bHeight = image.height() < 256 ? image.height() : 0; // 0 means 256
|
|
entries[i].bWidth = image.width() < 256 ? image.width() : 0; // 0 means 256
|
|
entries[i].dwBytesInRes = BMP_INFOHDR_SIZE + (bpl_bmp * image.height())
|
|
+ (maskImage.bytesPerLine() * maskImage.height());
|
|
entries[i].wPlanes = 1;
|
|
if (i == 0)
|
|
entries[i].dwImageOffset = origOffset + ICONDIR_SIZE
|
|
+ (id.idCount * ICONDIRENTRY_SIZE);
|
|
else
|
|
entries[i].dwImageOffset = entries[i-1].dwImageOffset + entries[i-1].dwBytesInRes;
|
|
|
|
bmpHeaders[i].biBitCount = entries[i].wBitCount;
|
|
bmpHeaders[i].biClrImportant = 0;
|
|
bmpHeaders[i].biClrUsed = entries[i].bColorCount;
|
|
bmpHeaders[i].biCompression = 0;
|
|
bmpHeaders[i].biHeight = entries[i].bHeight ? entries[i].bHeight * 2 : 256 * 2; // 2 is for the mask
|
|
bmpHeaders[i].biPlanes = entries[i].wPlanes;
|
|
bmpHeaders[i].biSize = BMP_INFOHDR_SIZE;
|
|
bmpHeaders[i].biSizeImage = entries[i].dwBytesInRes - BMP_INFOHDR_SIZE;
|
|
bmpHeaders[i].biWidth = entries[i].bWidth ? entries[i].bWidth : 256;
|
|
bmpHeaders[i].biXPelsPerMeter = 0;
|
|
bmpHeaders[i].biYPelsPerMeter = 0;
|
|
|
|
QBuffer buffer(&imageData[i]);
|
|
buffer.open(QIODevice::WriteOnly);
|
|
|
|
uchar *buf = new uchar[bpl_bmp];
|
|
uchar *b;
|
|
memset( buf, 0, bpl_bmp );
|
|
int y;
|
|
for (y = image.height() - 1; y >= 0; y--) { // write the image bits
|
|
// 32 bits
|
|
QRgb *p = (QRgb *)image.scanLine(y);
|
|
QRgb *end = p + image.width();
|
|
b = buf;
|
|
int x = 0;
|
|
while (p < end) {
|
|
*b++ = qBlue(*p);
|
|
*b++ = qGreen(*p);
|
|
*b++ = qRed(*p);
|
|
*b++ = qAlpha(*p);
|
|
if (qAlpha(*p) > 0) // Even mostly transparent pixels must not be masked away
|
|
maskImage.setPixel(x, y, 0);
|
|
p++;
|
|
x++;
|
|
}
|
|
buffer.write((char*)buf, bpl_bmp);
|
|
}
|
|
delete[] buf;
|
|
|
|
// NOTE! !! The mask is only flipped vertically - not horizontally !!
|
|
for (y = maskImage.height() - 1; y >= 0; y--)
|
|
buffer.write((char*)maskImage.scanLine(y), maskImage.bytesPerLine());
|
|
}
|
|
|
|
if (writeIconDir(device, id)) {
|
|
int i;
|
|
bool bOK = true;
|
|
for (i = 0; i < id.idCount && bOK; i++) {
|
|
bOK = writeIconDirEntry(device, entries[i]);
|
|
}
|
|
if (bOK) {
|
|
for (i = 0; i < id.idCount && bOK; i++) {
|
|
bOK = writeBMPInfoHeader(device, bmpHeaders[i]);
|
|
bOK &= (device->write(imageData[i]) == (int) imageData[i].size());
|
|
}
|
|
retValue = bOK;
|
|
}
|
|
}
|
|
|
|
delete [] entries;
|
|
delete [] bmpHeaders;
|
|
delete [] imageData;
|
|
|
|
}
|
|
return retValue;
|
|
}
|
|
|
|
/*!
|
|
Constructs an instance of QtIcoHandler initialized to use \a device.
|
|
*/
|
|
QtIcoHandler::QtIcoHandler(QIODevice *device)
|
|
{
|
|
m_currentIconIndex = 0;
|
|
setDevice(device);
|
|
m_pICOReader = new ICOReader(device);
|
|
}
|
|
|
|
/*!
|
|
Destructor for QtIcoHandler.
|
|
*/
|
|
QtIcoHandler::~QtIcoHandler()
|
|
{
|
|
delete m_pICOReader;
|
|
}
|
|
|
|
QVariant QtIcoHandler::option(ImageOption option) const
|
|
{
|
|
if (option == Size || option == ImageFormat) {
|
|
ICONDIRENTRY iconEntry;
|
|
if (m_pICOReader->readIconEntry(m_currentIconIndex, &iconEntry)) {
|
|
switch (option) {
|
|
case Size:
|
|
return QSize(iconEntry.bWidth ? iconEntry.bWidth : 256,
|
|
iconEntry.bHeight ? iconEntry.bHeight : 256);
|
|
|
|
case ImageFormat:
|
|
switch (iconEntry.wBitCount) {
|
|
case 2:
|
|
return QImage::Format_Mono;
|
|
case 24:
|
|
return QImage::Format_RGB32;
|
|
case 32:
|
|
return QImage::Format_ARGB32;
|
|
default:
|
|
return QImage::Format_Indexed8;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
bool QtIcoHandler::supportsOption(ImageOption option) const
|
|
{
|
|
return (option == Size || option == ImageFormat);
|
|
}
|
|
|
|
/*!
|
|
* Verifies if some values (magic bytes) are set as expected in the header of the file.
|
|
* If the magic bytes were found, it is assumed that the QtIcoHandler can read the file.
|
|
*
|
|
*/
|
|
bool QtIcoHandler::canRead() const
|
|
{
|
|
bool bCanRead = false;
|
|
QIODevice *device = QImageIOHandler::device();
|
|
if (device) {
|
|
bCanRead = ICOReader::canRead(device);
|
|
if (bCanRead)
|
|
setFormat("ico");
|
|
} else {
|
|
qCWarning(lcIco, "QtIcoHandler::canRead() called with no device");
|
|
}
|
|
return bCanRead;
|
|
}
|
|
|
|
/*! This static function is used by the plugin code, and is provided for convenience only.
|
|
\a device must be an opened device with pointing to the start of the header data of the ICO file.
|
|
*/
|
|
bool QtIcoHandler::canRead(QIODevice *device)
|
|
{
|
|
Q_ASSERT(device);
|
|
return ICOReader::canRead(device);
|
|
}
|
|
|
|
/*! \reimp
|
|
|
|
*/
|
|
bool QtIcoHandler::read(QImage *image)
|
|
{
|
|
bool bSuccess = false;
|
|
QImage img = m_pICOReader->iconAt(m_currentIconIndex);
|
|
|
|
// Make sure we only write to \a image when we succeed.
|
|
if (!img.isNull()) {
|
|
bSuccess = true;
|
|
*image = img;
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
/*! \reimp
|
|
|
|
*/
|
|
bool QtIcoHandler::write(const QImage &image)
|
|
{
|
|
QIODevice *device = QImageIOHandler::device();
|
|
QList<QImage> imgs;
|
|
imgs.append(image);
|
|
return ICOReader::write(device, imgs);
|
|
}
|
|
|
|
/*! \reimp
|
|
|
|
*/
|
|
int QtIcoHandler::imageCount() const
|
|
{
|
|
return m_pICOReader->count();
|
|
}
|
|
|
|
/*! \reimp
|
|
|
|
*/
|
|
bool QtIcoHandler::jumpToImage(int imageNumber)
|
|
{
|
|
if (imageNumber < imageCount()) {
|
|
m_currentIconIndex = imageNumber;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*! \reimp
|
|
|
|
*/
|
|
bool QtIcoHandler::jumpToNextImage()
|
|
{
|
|
return jumpToImage(m_currentIconIndex + 1);
|
|
}
|
|
|
|
QT_END_NAMESPACE
|