/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Copyright (C) 2016 Alex Char. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qicnshandler_p.h" #include #include #include #include #include #ifndef QT_NO_DATASTREAM QT_BEGIN_NAMESPACE static const quint8 ICNSBlockHeaderSize = 8; static const QRgb ICNSColorTableMono[] = { qRgb(0xFF, 0xFF, 0xFF), qRgb(0x00, 0x00, 0x00) }; Q_STATIC_ASSERT(sizeof(ICNSColorTableMono) / sizeof(ICNSColorTableMono[0]) == (1 << ICNSEntry::DepthMono)); static const QRgb ICNSColorTable4bit[] = { qRgb(0xFF, 0xFF, 0xFF), qRgb(0xFC, 0xF3, 0x05), qRgb(0xFF, 0x64, 0x02), qRgb(0xDD, 0x08, 0x06), qRgb(0xF2, 0x08, 0x84), qRgb(0x46, 0x00, 0xA5), qRgb(0x00, 0x00, 0xD4), qRgb(0x02, 0xAB, 0xEA), qRgb(0x1F, 0xB7, 0x14), qRgb(0x00, 0x64, 0x11), qRgb(0x56, 0x2C, 0x05), qRgb(0x90, 0x71, 0x3A), qRgb(0xC0, 0xC0, 0xC0), qRgb(0x80, 0x80, 0x80), qRgb(0x40, 0x40, 0x40), qRgb(0x00, 0x00, 0x00) }; Q_STATIC_ASSERT(sizeof(ICNSColorTable4bit) / sizeof(ICNSColorTable4bit[0]) == (1 << ICNSEntry::Depth4bit)); static const QRgb ICNSColorTable8bit[] = { qRgb(0xFF, 0xFF, 0xFF), qRgb(0xFF, 0xFF, 0xCC), qRgb(0xFF, 0xFF, 0x99), qRgb(0xFF, 0xFF, 0x66), qRgb(0xFF, 0xFF, 0x33), qRgb(0xFF, 0xFF, 0x00), qRgb(0xFF, 0xCC, 0xFF), qRgb(0xFF, 0xCC, 0xCC), qRgb(0xFF, 0xCC, 0x99), qRgb(0xFF, 0xCC, 0x66), qRgb(0xFF, 0xCC, 0x33), qRgb(0xFF, 0xCC, 0x00), qRgb(0xFF, 0x99, 0xFF), qRgb(0xFF, 0x99, 0xCC), qRgb(0xFF, 0x99, 0x99), qRgb(0xFF, 0x99, 0x66), qRgb(0xFF, 0x99, 0x33), qRgb(0xFF, 0x99, 0x00), qRgb(0xFF, 0x66, 0xFF), qRgb(0xFF, 0x66, 0xCC), qRgb(0xFF, 0x66, 0x99), qRgb(0xFF, 0x66, 0x66), qRgb(0xFF, 0x66, 0x33), qRgb(0xFF, 0x66, 0x00), qRgb(0xFF, 0x33, 0xFF), qRgb(0xFF, 0x33, 0xCC), qRgb(0xFF, 0x33, 0x99), qRgb(0xFF, 0x33, 0x66), qRgb(0xFF, 0x33, 0x33), qRgb(0xFF, 0x33, 0x00), qRgb(0xFF, 0x00, 0xFF), qRgb(0xFF, 0x00, 0xCC), qRgb(0xFF, 0x00, 0x99), qRgb(0xFF, 0x00, 0x66), qRgb(0xFF, 0x00, 0x33), qRgb(0xFF, 0x00, 0x00), qRgb(0xCC, 0xFF, 0xFF), qRgb(0xCC, 0xFF, 0xCC), qRgb(0xCC, 0xFF, 0x99), qRgb(0xCC, 0xFF, 0x66), qRgb(0xCC, 0xFF, 0x33), qRgb(0xCC, 0xFF, 0x00), qRgb(0xCC, 0xCC, 0xFF), qRgb(0xCC, 0xCC, 0xCC), qRgb(0xCC, 0xCC, 0x99), qRgb(0xCC, 0xCC, 0x66), qRgb(0xCC, 0xCC, 0x33), qRgb(0xCC, 0xCC, 0x00), qRgb(0xCC, 0x99, 0xFF), qRgb(0xCC, 0x99, 0xCC), qRgb(0xCC, 0x99, 0x99), qRgb(0xCC, 0x99, 0x66), qRgb(0xCC, 0x99, 0x33), qRgb(0xCC, 0x99, 0x00), qRgb(0xCC, 0x66, 0xFF), qRgb(0xCC, 0x66, 0xCC), qRgb(0xCC, 0x66, 0x99), qRgb(0xCC, 0x66, 0x66), qRgb(0xCC, 0x66, 0x33), qRgb(0xCC, 0x66, 0x00), qRgb(0xCC, 0x33, 0xFF), qRgb(0xCC, 0x33, 0xCC), qRgb(0xCC, 0x33, 0x99), qRgb(0xCC, 0x33, 0x66), qRgb(0xCC, 0x33, 0x33), qRgb(0xCC, 0x33, 0x00), qRgb(0xCC, 0x00, 0xFF), qRgb(0xCC, 0x00, 0xCC), qRgb(0xCC, 0x00, 0x99), qRgb(0xCC, 0x00, 0x66), qRgb(0xCC, 0x00, 0x33), qRgb(0xCC, 0x00, 0x00), qRgb(0x99, 0xFF, 0xFF), qRgb(0x99, 0xFF, 0xCC), qRgb(0x99, 0xFF, 0x99), qRgb(0x99, 0xFF, 0x66), qRgb(0x99, 0xFF, 0x33), qRgb(0x99, 0xFF, 0x00), qRgb(0x99, 0xCC, 0xFF), qRgb(0x99, 0xCC, 0xCC), qRgb(0x99, 0xCC, 0x99), qRgb(0x99, 0xCC, 0x66), qRgb(0x99, 0xCC, 0x33), qRgb(0x99, 0xCC, 0x00), qRgb(0x99, 0x99, 0xFF), qRgb(0x99, 0x99, 0xCC), qRgb(0x99, 0x99, 0x99), qRgb(0x99, 0x99, 0x66), qRgb(0x99, 0x99, 0x33), qRgb(0x99, 0x99, 0x00), qRgb(0x99, 0x66, 0xFF), qRgb(0x99, 0x66, 0xCC), qRgb(0x99, 0x66, 0x99), qRgb(0x99, 0x66, 0x66), qRgb(0x99, 0x66, 0x33), qRgb(0x99, 0x66, 0x00), qRgb(0x99, 0x33, 0xFF), qRgb(0x99, 0x33, 0xCC), qRgb(0x99, 0x33, 0x99), qRgb(0x99, 0x33, 0x66), qRgb(0x99, 0x33, 0x33), qRgb(0x99, 0x33, 0x00), qRgb(0x99, 0x00, 0xFF), qRgb(0x99, 0x00, 0xCC), qRgb(0x99, 0x00, 0x99), qRgb(0x99, 0x00, 0x66), qRgb(0x99, 0x00, 0x33), qRgb(0x99, 0x00, 0x00), qRgb(0x66, 0xFF, 0xFF), qRgb(0x66, 0xFF, 0xCC), qRgb(0x66, 0xFF, 0x99), qRgb(0x66, 0xFF, 0x66), qRgb(0x66, 0xFF, 0x33), qRgb(0x66, 0xFF, 0x00), qRgb(0x66, 0xCC, 0xFF), qRgb(0x66, 0xCC, 0xCC), qRgb(0x66, 0xCC, 0x99), qRgb(0x66, 0xCC, 0x66), qRgb(0x66, 0xCC, 0x33), qRgb(0x66, 0xCC, 0x00), qRgb(0x66, 0x99, 0xFF), qRgb(0x66, 0x99, 0xCC), qRgb(0x66, 0x99, 0x99), qRgb(0x66, 0x99, 0x66), qRgb(0x66, 0x99, 0x33), qRgb(0x66, 0x99, 0x00), qRgb(0x66, 0x66, 0xFF), qRgb(0x66, 0x66, 0xCC), qRgb(0x66, 0x66, 0x99), qRgb(0x66, 0x66, 0x66), qRgb(0x66, 0x66, 0x33), qRgb(0x66, 0x66, 0x00), qRgb(0x66, 0x33, 0xFF), qRgb(0x66, 0x33, 0xCC), qRgb(0x66, 0x33, 0x99), qRgb(0x66, 0x33, 0x66), qRgb(0x66, 0x33, 0x33), qRgb(0x66, 0x33, 0x00), qRgb(0x66, 0x00, 0xFF), qRgb(0x66, 0x00, 0xCC), qRgb(0x66, 0x00, 0x99), qRgb(0x66, 0x00, 0x66), qRgb(0x66, 0x00, 0x33), qRgb(0x66, 0x00, 0x00), qRgb(0x33, 0xFF, 0xFF), qRgb(0x33, 0xFF, 0xCC), qRgb(0x33, 0xFF, 0x99), qRgb(0x33, 0xFF, 0x66), qRgb(0x33, 0xFF, 0x33), qRgb(0x33, 0xFF, 0x00), qRgb(0x33, 0xCC, 0xFF), qRgb(0x33, 0xCC, 0xCC), qRgb(0x33, 0xCC, 0x99), qRgb(0x33, 0xCC, 0x66), qRgb(0x33, 0xCC, 0x33), qRgb(0x33, 0xCC, 0x00), qRgb(0x33, 0x99, 0xFF), qRgb(0x33, 0x99, 0xCC), qRgb(0x33, 0x99, 0x99), qRgb(0x33, 0x99, 0x66), qRgb(0x33, 0x99, 0x33), qRgb(0x33, 0x99, 0x00), qRgb(0x33, 0x66, 0xFF), qRgb(0x33, 0x66, 0xCC), qRgb(0x33, 0x66, 0x99), qRgb(0x33, 0x66, 0x66), qRgb(0x33, 0x66, 0x33), qRgb(0x33, 0x66, 0x00), qRgb(0x33, 0x33, 0xFF), qRgb(0x33, 0x33, 0xCC), qRgb(0x33, 0x33, 0x99), qRgb(0x33, 0x33, 0x66), qRgb(0x33, 0x33, 0x33), qRgb(0x33, 0x33, 0x00), qRgb(0x33, 0x00, 0xFF), qRgb(0x33, 0x00, 0xCC), qRgb(0x33, 0x00, 0x99), qRgb(0x33, 0x00, 0x66), qRgb(0x33, 0x00, 0x33), qRgb(0x33, 0x00, 0x00), qRgb(0x00, 0xFF, 0xFF), qRgb(0x00, 0xFF, 0xCC), qRgb(0x00, 0xFF, 0x99), qRgb(0x00, 0xFF, 0x66), qRgb(0x00, 0xFF, 0x33), qRgb(0x00, 0xFF, 0x00), qRgb(0x00, 0xCC, 0xFF), qRgb(0x00, 0xCC, 0xCC), qRgb(0x00, 0xCC, 0x99), qRgb(0x00, 0xCC, 0x66), qRgb(0x00, 0xCC, 0x33), qRgb(0x00, 0xCC, 0x00), qRgb(0x00, 0x99, 0xFF), qRgb(0x00, 0x99, 0xCC), qRgb(0x00, 0x99, 0x99), qRgb(0x00, 0x99, 0x66), qRgb(0x00, 0x99, 0x33), qRgb(0x00, 0x99, 0x00), qRgb(0x00, 0x66, 0xFF), qRgb(0x00, 0x66, 0xCC), qRgb(0x00, 0x66, 0x99), qRgb(0x00, 0x66, 0x66), qRgb(0x00, 0x66, 0x33), qRgb(0x00, 0x66, 0x00), qRgb(0x00, 0x33, 0xFF), qRgb(0x00, 0x33, 0xCC), qRgb(0x00, 0x33, 0x99), qRgb(0x00, 0x33, 0x66), qRgb(0x00, 0x33, 0x33), qRgb(0x00, 0x33, 0x00), qRgb(0x00, 0x00, 0xFF), qRgb(0x00, 0x00, 0xCC), qRgb(0x00, 0x00, 0x99), qRgb(0x00, 0x00, 0x66), qRgb(0x00, 0x00, 0x33), qRgb(0xEE, 0x00, 0x00), qRgb(0xDD, 0x00, 0x00), qRgb(0xBB, 0x00, 0x00), qRgb(0xAA, 0x00, 0x00), qRgb(0x88, 0x00, 0x00), qRgb(0x77, 0x00, 0x00), qRgb(0x55, 0x00, 0x00), qRgb(0x44, 0x00, 0x00), qRgb(0x22, 0x00, 0x00), qRgb(0x11, 0x00, 0x00), qRgb(0x00, 0xEE, 0x00), qRgb(0x00, 0xDD, 0x00), qRgb(0x00, 0xBB, 0x00), qRgb(0x00, 0xAA, 0x00), qRgb(0x00, 0x88, 0x00), qRgb(0x00, 0x77, 0x00), qRgb(0x00, 0x55, 0x00), qRgb(0x00, 0x44, 0x00), qRgb(0x00, 0x22, 0x00), qRgb(0x00, 0x11, 0x00), qRgb(0x00, 0x00, 0xEE), qRgb(0x00, 0x00, 0xDD), qRgb(0x00, 0x00, 0xBB), qRgb(0x00, 0x00, 0xAA), qRgb(0x00, 0x00, 0x88), qRgb(0x00, 0x00, 0x77), qRgb(0x00, 0x00, 0x55), qRgb(0x00, 0x00, 0x44), qRgb(0x00, 0x00, 0x22), qRgb(0x00, 0x00, 0x11), qRgb(0xEE, 0xEE, 0xEE), qRgb(0xDD, 0xDD, 0xDD), qRgb(0xBB, 0xBB, 0xBB), qRgb(0xAA, 0xAA, 0xAA), qRgb(0x88, 0x88, 0x88), qRgb(0x77, 0x77, 0x77), qRgb(0x55, 0x55, 0x55), qRgb(0x44, 0x44, 0x44), qRgb(0x22, 0x22, 0x22), qRgb(0x11, 0x11, 0x11), qRgb(0x00, 0x00, 0x00) }; Q_STATIC_ASSERT(sizeof(ICNSColorTable8bit) / sizeof(ICNSColorTable8bit[0]) == (1 << ICNSEntry::Depth8bit)); static inline QDataStream &operator>>(QDataStream &in, ICNSBlockHeader &p) { in >> p.ostype; in >> p.length; return in; } static inline QDataStream &operator<<(QDataStream &out, const ICNSBlockHeader &p) { out << p.ostype; out << p.length; return out; } static inline bool isPowOf2OrDividesBy16(quint32 u, qreal r) { return u == r && ((u % 16 == 0) || (r >= 16 && (u & (u - 1)) == 0)); } static inline bool isBlockHeaderValid(const ICNSBlockHeader &header, quint64 bound = 0) { return header.ostype != 0 && (bound == 0 || qBound(quint64(ICNSBlockHeaderSize), quint64(header.length), bound) == header.length); } static inline bool isIconCompressed(const ICNSEntry &icon) { return icon.dataFormat == ICNSEntry::PNG || icon.dataFormat == ICNSEntry::JP2; } static inline bool isMaskSuitable(const ICNSEntry &mask, const ICNSEntry &icon, ICNSEntry::Depth target) { return mask.variant == icon.variant && mask.depth == target && mask.height == icon.height && mask.width == icon.width; } static inline QByteArray nameFromOSType(quint32 ostype) { const quint32 bytes = qToBigEndian(ostype); return QByteArray((const char*)&bytes, 4); } static inline quint32 nameToOSType(const QByteArray &ostype) { if (ostype.size() != 4) return 0; return qFromBigEndian(*reinterpret_cast(ostype.constData())); } static inline QByteArray nameForCompressedIcon(quint8 iconNumber) { const bool portable = iconNumber < 7; const QByteArray base = portable ? QByteArrayLiteral("icp") : QByteArrayLiteral("ic"); if (!portable && iconNumber < 10) return base + "0" + QByteArray::number(iconNumber); return base + QByteArray::number(iconNumber); } static inline QVector getColorTable(ICNSEntry::Depth depth) { QVector table; uint n = 1 << depth; const QRgb *data; switch (depth) { case ICNSEntry::DepthMono: data = ICNSColorTableMono; break; case ICNSEntry::Depth4bit: data = ICNSColorTable4bit; break; case ICNSEntry::Depth8bit: data = ICNSColorTable8bit; break; default: Q_UNREACHABLE(); break; } table.resize(n); memcpy(table.data(), data, sizeof(QRgb) * n); return table; } static bool parseIconEntryData(ICNSEntry &icon, QIODevice *device) { const qint64 oldPos = device->pos(); if (oldPos != icon.dataOffset && !device->seek(icon.dataOffset)) return false; const QByteArray magic = device->peek(12); const bool isPNG = magic.startsWith(QByteArrayLiteral("\211PNG\r\n\032\n\000\000\000\r")); const bool isJP2 = !isPNG && magic == QByteArrayLiteral("\000\000\000\014jP \r\n\207\n"); if (isPNG || isJP2) { // TODO: Add parsing of png/jp2 headers to enable feature reporting by plugin? icon.flags = ICNSEntry::IsIcon; icon.dataFormat = isPNG? ICNSEntry::PNG : ICNSEntry::JP2; } if (oldPos != icon.dataOffset && !device->seek(oldPos)) return false; return true; } static bool parseIconEntryInfo(ICNSEntry &icon) { const QString ostype = QString::fromLatin1(nameFromOSType(icon.ostype)); // Typical OSType naming: ; // For icons OSType should be strictly alphanumeric + '#' character for masks/mono. const QString ptrn = QStringLiteral("^(?[a-z|A-Z]{0,4})(?[a-z|A-Z]{1})(?[\\d]{0,2})(?[#mk]{0,2})$"); QRegularExpression regexp(ptrn); QRegularExpressionMatch match = regexp.match(ostype); if (!match.hasMatch()) { qWarning("parseIconEntryInfo(): Failed, OSType doesn't match: \"%s\"", qPrintable(ostype)); return false; } const QString group = match.captured(QStringLiteral("group")); const QString depth = match.captured(QStringLiteral("depth")); const QString mask = match.captured(QStringLiteral("mask")); // Icon group: if (!group.isEmpty()) icon.group = ICNSEntry::Group(group.at(0).toLatin1()); // That's enough for compressed ones if (isIconCompressed(icon)) return true; // Icon depth: if (!depth.isEmpty()) icon.depth = ICNSEntry::Depth(depth.toUInt()); // Try mono if depth not found if (icon.depth == ICNSEntry::DepthUnknown) icon.depth = ICNSEntry::DepthMono; // Detect size: const qreal bytespp = (qreal)icon.depth / 8; const qreal r1 = qSqrt(icon.dataLength / bytespp); const qreal r2 = qSqrt((icon.dataLength / bytespp) / 2); const quint32 r1u = qRound(r1); const quint32 r2u = qRound(r2); const bool singleEntry = isPowOf2OrDividesBy16(r1u, r1); const bool doubleSize = isPowOf2OrDividesBy16(r2u, r2); if (singleEntry) { icon.flags = mask.isEmpty() ? ICNSEntry::IsIcon : ICNSEntry::IsMask; icon.dataFormat = ICNSEntry::RawIcon; icon.width = r1u; icon.height = r1u; } else if (doubleSize) { icon.flags = ICNSEntry::IconPlusMask; icon.dataFormat = ICNSEntry::RawIcon; icon.width = r2u; icon.height = r2u; } else if (icon.group == ICNSEntry::GroupMini) { // Legacy 16x12 icons are an exception from the generic square formula const bool doubleSize = icon.dataLength == 192 * bytespp * 2; icon.flags = doubleSize ? ICNSEntry::IconPlusMask : ICNSEntry::IsIcon; icon.dataFormat = ICNSEntry::RawIcon; icon.width = 16; icon.height = 12; } else if (icon.depth == ICNSEntry::Depth32bit) { // We have a formula mismatch in a 32bit icon there, probably RLE24 icon.dataFormat = ICNSEntry::RLE24; icon.flags = mask.isEmpty() ? ICNSEntry::IsIcon : ICNSEntry::IsMask; switch (icon.group) { case ICNSEntry::GroupSmall: icon.width = 16; break; case ICNSEntry::GroupLarge: icon.width = 32; break; case ICNSEntry::GroupHuge: icon.width = 48; break; case ICNSEntry::GroupThumbnail: icon.width = 128; break; default: qWarning("parseIconEntryInfo(): Failed, 32bit icon from an unknown group. OSType: \"%s\"", qPrintable(ostype)); } icon.height = icon.width; } return true; } static QImage readMask(const ICNSEntry &mask, QDataStream &stream) { if ((mask.flags & ICNSEntry::IsMask) == 0) return QImage(); if (mask.depth != ICNSEntry::DepthMono && mask.depth != ICNSEntry::Depth8bit) { qWarning("readMask(): Failed, unusual bit depth: %u OSType: \"%s\"", mask.depth, nameFromOSType(mask.ostype).constData()); return QImage(); } const bool isMono = mask.depth == ICNSEntry::DepthMono; const bool doubleSize = mask.flags == ICNSEntry::IconPlusMask; const quint32 imageDataSize = (mask.width * mask.height * mask.depth) / 8; const qint64 pos = doubleSize ? (mask.dataOffset + imageDataSize) : mask.dataOffset; const qint64 oldPos = stream.device()->pos(); if (!stream.device()->seek(pos)) return QImage(); QImage img(mask.width, mask.height, QImage::Format_RGB32); quint8 byte = 0; quint32 pixel = 0; for (quint32 y = 0; y < mask.height; y++) { QRgb *line = reinterpret_cast(img.scanLine(y)); for (quint32 x = 0; x < mask.width; x++) { if (pixel % (8 / mask.depth) == 0) stream >> byte; else if (isMono) byte <<= 1; const quint8 alpha = isMono ? (((byte >> 7) & 0x01) * 255) : byte; line[x] = qRgb(alpha, alpha, alpha); pixel++; } } stream.device()->seek(oldPos); return img; } template static QImage readLowDepthIcon(const ICNSEntry &icon, QDataStream &stream) { Q_STATIC_ASSERT(depth == ICNSEntry::DepthMono || depth == ICNSEntry::Depth4bit || depth == ICNSEntry::Depth8bit); const bool isMono = depth == ICNSEntry::DepthMono; const QImage::Format format = isMono ? QImage::Format_Mono : QImage::Format_Indexed8; const QVector colortable = getColorTable(depth); if (colortable.isEmpty()) return QImage(); QImage img(icon.width, icon.height, format); img.setColorTable(colortable); quint32 pixel = 0; quint8 byte = 0; for (quint32 y = 0; y < icon.height; y++) { for (quint32 x = 0; x < icon.width; x++) { if (pixel % (8 / depth) == 0) stream >> byte; quint8 cindex; switch (depth) { case ICNSEntry::DepthMono: cindex = (byte >> 7) & 0x01; // left 1 bit byte <<= 1; break; case ICNSEntry::Depth4bit: cindex = (byte >> 4) & 0x0F; // left 4 bits byte <<= 4; break; default: cindex = byte; // 8 bits break; } img.setPixel(x, y, cindex); pixel++; } } return img; } static QImage read32bitIcon(const ICNSEntry &icon, QDataStream &stream) { QImage img = QImage(icon.width, icon.height, QImage::Format_RGB32); if (icon.dataFormat != ICNSEntry::RLE24) { for (quint32 y = 0; y < icon.height; y++) { QRgb *line = reinterpret_cast(img.scanLine(y)); for (quint32 x = 0; x < icon.width; x++) { quint8 r, g, b, a; stream >> r >> g >> b >> a; line[x] = qRgb(r, g, b); } } } else { const quint32 estPxsNum = icon.width * icon.height; const QByteArray &bytes = stream.device()->peek(4); if (bytes.isEmpty()) return QImage(); // Zero-padding may be present: if (qFromBigEndian(*bytes.constData()) == 0) stream.skipRawData(4); for (quint8 colorNRun = 0; colorNRun < 3; colorNRun++) { quint32 pixel = 0; QRgb *line = 0; while (pixel < estPxsNum && !stream.atEnd()) { quint8 byte, value; stream >> byte; const bool bitIsClear = (byte & 0x80) == 0; // If high bit is clear: run of different values; else: same value quint8 runLength = bitIsClear ? ((0xFF & byte) + 1) : ((0xFF & byte) - 125); // Length of the run for for different values: 1 <= len <= 128 // Length of the run for same values: 3 <= len <= 130 if (!bitIsClear) stream >> value; for (quint8 i = 0; i < runLength && pixel < estPxsNum; i++) { if (bitIsClear) stream >> value; const quint32 y = pixel / icon.height; const quint32 x = pixel - (icon.width * y); if (pixel % icon.height == 0) line = reinterpret_cast(img.scanLine(y)); QRgb rgb = line[x]; const int r = (colorNRun == 0) ? value : qRed(rgb); const int g = (colorNRun == 1) ? value : qGreen(rgb); const int b = (colorNRun == 2) ? value : qBlue(rgb); line[x] = qRgb(r, g, b); pixel++; } } } } return img; } QICNSHandler::QICNSHandler() : m_currentIconIndex(0), m_state(ScanNotScanned) { } #if QT_DEPRECATED_SINCE(5, 13) QByteArray QICNSHandler::name() const { return QByteArrayLiteral("icns"); } #endif bool QICNSHandler::canRead(QIODevice *device) { if (!device || !device->isReadable()) { qWarning("QICNSHandler::canRead() called without a readable device"); return false; } if (device->peek(4) == QByteArrayLiteral("icns")) { if (device->isSequential()) { qWarning("QICNSHandler::canRead() called on a sequential device"); return false; } return true; } return false; } bool QICNSHandler::canRead() const { if (m_state == ScanNotScanned && !canRead(device())) return false; if (m_state != ScanError) { setFormat(QByteArrayLiteral("icns")); return true; } return false; } bool QICNSHandler::read(QImage *outImage) { QImage img; if (!ensureScanned()) { qWarning("QICNSHandler::read(): The device wasn't parsed properly!"); return false; } const ICNSEntry &icon = m_icons.at(m_currentIconIndex); QDataStream stream(device()); stream.setByteOrder(QDataStream::BigEndian); if (!device()->seek(icon.dataOffset)) return false; switch (icon.dataFormat) { case ICNSEntry::RawIcon: case ICNSEntry::RLE24: if (qMin(icon.width, icon.height) == 0) break; switch (icon.depth) { case ICNSEntry::DepthMono: img = readLowDepthIcon(icon, stream); break; case ICNSEntry::Depth4bit: img = readLowDepthIcon(icon, stream); break; case ICNSEntry::Depth8bit: img = readLowDepthIcon(icon, stream); break; case ICNSEntry::Depth32bit: img = read32bitIcon(icon, stream); break; default: qWarning("QICNSHandler::read(): Failed, unsupported icon bit depth: %u, OSType: \"%s\"", icon.depth, nameFromOSType(icon.ostype).constData()); } if (!img.isNull()) { QImage alpha = readMask(getIconMask(icon), stream); if (!alpha.isNull()) img.setAlphaChannel(alpha); } break; default: const char *format = 0; if (icon.dataFormat == ICNSEntry::PNG) format = "png"; else if (icon.dataFormat == ICNSEntry::JP2) format = "jp2"; // Even if JP2 or PNG magic is not detected, try anyway for unknown formats img = QImage::fromData(device()->read(icon.dataLength), format); if (img.isNull()) { if (format == 0) format = "unknown"; qWarning("QICNSHandler::read(): Failed, compressed format \"%s\" is not supported " \ "by your Qt library or this file is corrupt. OSType: \"%s\"", format, nameFromOSType(icon.ostype).constData()); } } *outImage = img; return !img.isNull(); } bool QICNSHandler::write(const QImage &image) { /* Notes: * Experimental implementation. Just for simple converting tasks / testing purposes. * Min. size is 16x16, Max. size is 1024x1024. * Performs downscale to a square image if width != height. * Performs upscale to 16x16, if the image is smaller. * Performs downscale to a nearest power of two if size is not a power of two. * Currently uses non-hardcoded OSTypes. */ QIODevice *device = this->device(); if (!device->isWritable() || image.isNull() || qMin(image.width(), image.height()) == 0) return false; const int minSize = qMin(image.width(), image.height()); const int oldSize = (minSize < 16) ? 16 : minSize; // Calc power of two: int size = oldSize; uint pow = 0; // Note: Values over 10 are reserved for retina icons. while (pow < 10 && (size >>= 1)) pow++; const int newSize = 1 << pow; QImage img = image; // Let's enforce resizing if size differs: if (newSize != oldSize || qMax(image.width(), image.height()) != minSize) img = img.scaled(newSize, newSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); // Construct OSType and headers: const quint32 ostype = nameToOSType(nameForCompressedIcon(pow)); ICNSBlockHeader fileHeader; fileHeader.ostype = ICNSBlockHeader::TypeIcns; ICNSBlockHeader tocHeader; tocHeader.ostype = ICNSBlockHeader::TypeToc; ICNSBlockHeader iconEntry; iconEntry.ostype = ostype; QByteArray imageData; QBuffer buffer(&imageData); if (!buffer.open(QIODevice::WriteOnly) || !img.save(&buffer, "png")) return false; buffer.close(); iconEntry.length = ICNSBlockHeaderSize + imageData.size(); tocHeader.length = ICNSBlockHeaderSize * 2; fileHeader.length = ICNSBlockHeaderSize + tocHeader.length + iconEntry.length; if (!isBlockHeaderValid(iconEntry)) return false; QDataStream stream(device); // iconEntry is also a TOC entry stream << fileHeader << tocHeader << iconEntry << iconEntry; stream.writeRawData(imageData.constData(), imageData.size()); return stream.status() == QDataStream::Ok; } bool QICNSHandler::supportsOption(ImageOption option) const { return option == SubType; } QVariant QICNSHandler::option(ImageOption option) const { if (!supportsOption(option) || !ensureScanned()) return QVariant(); if (option == SubType) { if (imageCount() > 0 && m_currentIconIndex <= imageCount()) { const ICNSEntry &icon = m_icons.at(m_currentIconIndex); if (icon.variant != 0) return QByteArray(nameFromOSType(icon.variant) + '-' + nameFromOSType(icon.ostype)); return nameFromOSType(icon.ostype); } } return QVariant(); } int QICNSHandler::imageCount() const { if (!ensureScanned()) return 0; return m_icons.size(); } bool QICNSHandler::jumpToImage(int imageNumber) { if (imageNumber >= imageCount()) return false; m_currentIconIndex = imageNumber; return true; } bool QICNSHandler::jumpToNextImage() { return jumpToImage(m_currentIconIndex + 1); } bool QICNSHandler::ensureScanned() const { if (m_state == ScanNotScanned) { QICNSHandler *that = const_cast(this); that->m_state = that->scanDevice() ? ScanSuccess : ScanError; } return m_state == ScanSuccess; } bool QICNSHandler::addEntry(const ICNSBlockHeader &header, qint64 imgDataOffset, quint32 variant) { // Note: This function returns false only when a device positioning error occurred ICNSEntry entry; entry.ostype = header.ostype; entry.variant = variant; entry.dataOffset = imgDataOffset; entry.dataLength = header.length - ICNSBlockHeaderSize; // Check for known magic numbers: if (!parseIconEntryData(entry, device())) return false; // Parse everything else and index this entry: if (parseIconEntryInfo(entry)) { if ((entry.flags & ICNSEntry::IsMask) != 0) m_masks << entry; if ((entry.flags & ICNSEntry::IsIcon) != 0) m_icons << entry; } return true; } bool QICNSHandler::scanDevice() { if (m_state == ScanSuccess) return true; if (!device()->seek(0)) return false; QDataStream stream(device()); stream.setByteOrder(QDataStream::BigEndian); bool scanIsIncomplete = false; qint64 filelength = device()->size(); ICNSBlockHeader blockHeader; while (!stream.atEnd() || device()->pos() < filelength) { stream >> blockHeader; if (stream.status() != QDataStream::Ok) return false; const qint64 blockDataOffset = device()->pos(); if (!isBlockHeaderValid(blockHeader)) { qWarning("QICNSHandler::scanDevice(): Failed, bad header at pos %s. OSType \"%s\", length %u", QByteArray::number(blockDataOffset).constData(), nameFromOSType(blockHeader.ostype).constData(), blockHeader.length); return false; } const quint64 blockDataLength = blockHeader.length - ICNSBlockHeaderSize; const qint64 nextBlockOffset = blockDataOffset + blockDataLength; switch (blockHeader.ostype) { case ICNSBlockHeader::TypeIcns: if (blockDataOffset != ICNSBlockHeaderSize) { // Icns container definition should be in the beginning of the device. // If we meet this block somewhere else, then just ignore it. stream.skipRawData(blockDataLength); break; } filelength = blockHeader.length; if (device()->size() < blockHeader.length) { qWarning("QICNSHandler::scanDevice(): Failed, file is incomplete."); return false; } break; case ICNSBlockHeader::TypeIcnv: case ICNSBlockHeader::TypeClut: // We don't have a good use for these blocks... yet. stream.skipRawData(blockDataLength); break; case ICNSBlockHeader::TypeTile: case ICNSBlockHeader::TypeOver: case ICNSBlockHeader::TypeOpen: case ICNSBlockHeader::TypeDrop: case ICNSBlockHeader::TypeOdrp: // Icns container seems to have an embedded icon variant container // Let's start a scan for entries while (device()->pos() < nextBlockOffset) { ICNSBlockHeader icon; stream >> icon; // Check for incorrect variant entry header and stop scan if (!isBlockHeaderValid(icon, blockDataLength)) break; if (!addEntry(icon, device()->pos(), blockHeader.ostype)) return false; if (stream.skipRawData(icon.length - ICNSBlockHeaderSize) < 0) return false; } if (device()->pos() != nextBlockOffset) { // Scan of this container didn't end where we expected. // Let's generate some output about this incident: qWarning("Scan of the icon variant container (\"%s\") failed at pos %s.\n" \ "Reason: Scan didn't reach the end of this container's block, " \ "delta: %s bytes. This file may be corrupted.", nameFromOSType(blockHeader.ostype).constData(), QByteArray::number(device()->pos()).constData(), QByteArray::number(nextBlockOffset - device()->pos()).constData()); if (!device()->seek(nextBlockOffset)) return false; } break; case ICNSBlockHeader::TypeToc: { // Quick scan, table of contents if (blockDataOffset != ICNSBlockHeaderSize * 2) { // TOC should be the first block in the file after container definition. // Ignore and go on with a deep scan. stream.skipRawData(blockDataLength); break; } // First image data offset: qint64 imgDataOffset = blockDataOffset + blockHeader.length; for (uint i = 0, count = blockDataLength / ICNSBlockHeaderSize; i < count; i++) { ICNSBlockHeader tocEntry; stream >> tocEntry; if (!isBlockHeaderValid(tocEntry)) { // TOC contains incorrect header, we should skip TOC since we can't trust it qWarning("QICNSHandler::scanDevice(): Warning! Table of contents contains a bad " \ "entry! Stop at device pos: %s bytes. This file may be corrupted.", QByteArray::number(device()->pos()).constData()); if (!device()->seek(nextBlockOffset)) return false; break; } if (!addEntry(tocEntry, imgDataOffset)) return false; imgDataOffset += tocEntry.length; // If TOC covers all the blocks in the file, then quick scan is complete if (imgDataOffset == filelength) return true; } // Else just start a deep scan to salvage anything left after TOC's end scanIsIncomplete = true; break; } default: // Deep scan, block by block if (scanIsIncomplete) { // Check if entry with this offset is added somewhere // But only if we have incomplete TOC, otherwise just try to add bool exists = false; for (int i = 0; i < m_icons.size() && !exists; i++) exists = m_icons.at(i).dataOffset == blockDataOffset; for (int i = 0; i < m_masks.size() && !exists; i++) exists = m_masks.at(i).dataOffset == blockDataOffset; if (!exists && !addEntry(blockHeader, blockDataOffset)) return false; } else if (!addEntry(blockHeader, blockDataOffset)) { return false; } stream.skipRawData(blockDataLength); break; } } return true; } const ICNSEntry &QICNSHandler::getIconMask(const ICNSEntry &icon) const { const bool is32bit = icon.depth == ICNSEntry::Depth32bit; ICNSEntry::Depth targetDepth = is32bit ? ICNSEntry::Depth8bit : ICNSEntry::DepthMono; for (int i = 0; i < m_masks.size(); i++) { const ICNSEntry &mask = m_masks.at(i); if (isMaskSuitable(mask, icon, targetDepth)) return mask; } return icon; } QT_END_NAMESPACE #endif // QT_NO_DATASTREAM