diff --git a/dist/changes-5.3.0 b/dist/changes-5.3.0 index e73f6bf4c2c548254bc4246dddcb5f676e9c0219..5a7a19b60dad854be8bcf269691687e2857b8e14 100644 --- a/dist/changes-5.3.0 +++ b/dist/changes-5.3.0 @@ -20,3 +20,4 @@ information about a particular change. **************************************************************************** - Add read/write support for Direct Draw Surface images. + - Add read/write support for ICNS images. diff --git a/src/imageformats/doc/src/qtimageformats.qdoc b/src/imageformats/doc/src/qtimageformats.qdoc index efd273d4e08ecbb83aeb80e81124c5393ffe6522..2c9f8a3207e6c0ce48ec55e1a5c1dbfa48c4acf3 100644 --- a/src/imageformats/doc/src/qtimageformats.qdoc +++ b/src/imageformats/doc/src/qtimageformats.qdoc @@ -53,6 +53,7 @@ libraries. If not found, it may fall back on using a bundled copy (in \table \header \li Format \li Description \li Support \li 3rd party codec \row \li DDS \li Direct Draw Surface \li Read/write \li No +\row \li ICNS \li Apple Icon Image \li Read/write \li No \row \li MNG \li Multiple-image Network Graphics \li Read \li Yes (bundled) \row \li TGA \li Truevision Graphics Adapter \li Read \li No \row \li TIFF \li Tagged Image File Format \li Read/write \li Yes (bundled) diff --git a/src/plugins/imageformats/icns/README b/src/plugins/imageformats/icns/README new file mode 100644 index 0000000000000000000000000000000000000000..e2fb24d474ff8436aef2f081998548942c5da439 --- /dev/null +++ b/src/plugins/imageformats/icns/README @@ -0,0 +1,3 @@ +[Useful links] +* http://en.wikipedia.org/wiki/Apple_Icon_Image_format - Wikipedia article about ICNS format +* http://www.macdisk.com/maciconen.php - Unofficial tech info about the format \ No newline at end of file diff --git a/src/plugins/imageformats/icns/icns.json b/src/plugins/imageformats/icns/icns.json new file mode 100644 index 0000000000000000000000000000000000000000..860b40c13964b319d1b100ac2c4dc3e04bbfb464 --- /dev/null +++ b/src/plugins/imageformats/icns/icns.json @@ -0,0 +1,4 @@ +{ + "Keys": [ "icns" ], + "MimeTypes": [ "image/x-icns" ] +} diff --git a/src/plugins/imageformats/icns/icns.pro b/src/plugins/imageformats/icns/icns.pro new file mode 100644 index 0000000000000000000000000000000000000000..eb57df5e5205043f1d52e3adaae3929c765e4506 --- /dev/null +++ b/src/plugins/imageformats/icns/icns.pro @@ -0,0 +1,16 @@ +TARGET = qicns + +PLUGIN_TYPE = imageformats +PLUGIN_CLASS_NAME = QICNSPlugin +load(qt_plugin) + +DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII + +HEADERS += \ + qicnshandler_p.h + +SOURCES += \ + main.cpp \ + qicnshandler.cpp + +OTHER_FILES += icns.json diff --git a/src/plugins/imageformats/icns/main.cpp b/src/plugins/imageformats/icns/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..62d1f8de7c41105c684d4dc5db6241719242ef6b --- /dev/null +++ b/src/plugins/imageformats/icns/main.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 Alex Char. +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_IMAGEFORMATPLUGIN + +#include "qicnshandler_p.h" + +#ifndef QT_NO_DATASTREAM + +QT_BEGIN_NAMESPACE + +class QICNSPlugin : public QImageIOPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "icns.json") + +public: + Capabilities capabilities(QIODevice *device, const QByteArray &format) const; + QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const; +}; + +QImageIOPlugin::Capabilities QICNSPlugin::capabilities(QIODevice *device, const QByteArray &format) const +{ + if (format == QByteArrayLiteral("icns")) + return Capabilities(CanRead | CanWrite); + if (!format.isEmpty()) + return 0; + if (!device || !device->isOpen()) + return 0; + + Capabilities cap; + if (device->isReadable() && QICNSHandler::canRead(device)) + cap |= CanRead; + if (device->isWritable()) + cap |= CanWrite; + return cap; +} + +QImageIOHandler *QICNSPlugin::create(QIODevice *device, const QByteArray &format) const +{ + QImageIOHandler *handler = new QICNSHandler(); + handler->setDevice(device); + handler->setFormat(format); + return handler; +} + +QT_END_NAMESPACE + +#include "main.moc" + +#endif // QT_NO_DATASTREAM + +#endif // QT_NO_IMAGEFORMATPLUGIN diff --git a/src/plugins/imageformats/icns/qicnshandler.cpp b/src/plugins/imageformats/icns/qicnshandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d9197629db01eba8b5266f33dd6809b2e097d5bd --- /dev/null +++ b/src/plugins/imageformats/icns/qicnshandler.cpp @@ -0,0 +1,959 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 Alex Char. +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qicnshandler_p.h" + +#include <QtCore/qmath.h> +#include <QtCore/qendian.h> +#include <QtCore/qregularexpression.h> +#include <QtCore/qbuffer.h> +#include <QtGui/qimage.h> + +#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) +{ + return header.ostype != 0 && header.length >= ICNSBlockHeaderSize; +} + +static inline bool isIconCompressed(const ICNSEntry &icon) +{ + return icon.group == ICNSEntry::GroupCompressed || icon.group == ICNSEntry::GroupPortable; +} + +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<const quint32*>(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<QRgb> getColorTable(ICNSEntry::Depth depth) +{ + QVector<QRgb> 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 parseIconEntry(ICNSEntry &icon) +{ + const QString ostype = QString::fromLatin1(nameFromOSType(icon.header.ostype)); + // Typical OSType naming: <junk><group><depth><mask>; + // For icons OSType should be strictly alphanumeric. + const QString ptrn = QStringLiteral("^(?<junk>[a-z|A-Z]{0,4})(?<group>[a-z|A-Z]{1})(?<depth>[\\d]{0,2})(?<mask>[#mk]{0,2})$"); + QRegularExpression regexp(ptrn); + QRegularExpressionMatch match = regexp.match(ostype); + if (!match.hasMatch()) { + qWarning("parseIconEntry(): 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: + icon.group = group.isEmpty() ? ICNSEntry::GroupUnknown : ICNSEntry::Group(group.at(0).toLatin1()); + const bool compressed = isIconCompressed(icon); + // Icon depth: + icon.depth = depth.isEmpty() ? ICNSEntry::DepthUnknown : ICNSEntry::Depth(depth.toUInt()); + // Width/height/mask: + icon.width = 0; + icon.height = 0; + icon.mask = ICNSEntry::MaskUnknown; + if (!compressed) { + if (icon.depth == ICNSEntry::DepthUnknown) + icon.depth = ICNSEntry::DepthMono; + 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.mask = mask.isEmpty() ? ICNSEntry::IsIcon : ICNSEntry::IsMask; + icon.width = r1u; + icon.height = r1u; + } else if (doubleSize) { + icon.mask = ICNSEntry::IconPlusMask; + 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.mask = doubleSize ? ICNSEntry::IconPlusMask : ICNSEntry::IsIcon; + icon.width = 16; + icon.height = 12; + } else if (icon.depth == ICNSEntry::Depth32bit) { + // 32bit icon may be encoded + icon.dataIsRLE = true; + icon.mask = 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("parseIconEntry(): Failed, 32bit icon from an unknown group. OSType: \"%s\"", + qPrintable(ostype)); + } + icon.height = icon.width; + } + } else { + // TODO: Add parsing of png/jp2 headers to enable feature reporting by plugin? + icon.mask = ICNSEntry::IsIcon; + } + return compressed || qMin(icon.width, icon.height) > 0; +} + +static QImage readMask(const ICNSEntry &mask, QDataStream &stream) +{ + if ((mask.mask & 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.header.ostype).constData()); + return QImage(); + } + const bool isMono = mask.depth == ICNSEntry::DepthMono; + const bool doubleSize = mask.mask == 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<QRgb *>(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 <ICNSEntry::Depth depth> +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<QRgb> 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.dataIsRLE) { + for (quint32 y = 0; y < icon.height; y++) { + QRgb *line = reinterpret_cast<QRgb *>(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<quint32>(*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<QRgb *>(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) +{ +} + +QByteArray QICNSHandler::name() const +{ + return QByteArrayLiteral("icns"); +} + +bool QICNSHandler::canRead(QIODevice *device) +{ + if (!device || !device->isReadable()) { + qWarning("QICNSHandler::canRead() called without a readable device"); + return false; + } + + if (device->isSequential()) { + qWarning("QICNSHandler::canRead() called on a sequential device (NYI)"); + return false; + } + + return device->peek(4) == QByteArrayLiteral("icns"); +} + +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 was not 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; + + 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 || isIconCompressed(icon)) { + const QByteArray ba = device()->read(icon.dataLength); + if (ba.isEmpty()) { + qWarning("QICNSHandler::read(): Failed, compressed image data is empty. OSType: \"%s\"", + nameFromOSType(icon.header.ostype).constData()); + return false; + } + const char *format = 0; + if (isPNG) + format = "png"; + else if (isJP2) + format = "jp2"; + // Even if JP2 or PNG magic is not detected, try anyway + img = QImage::fromData(ba, format); + if (img.isNull()) { + if (format == 0) + format = "unknown"; + qWarning("QICNSHandler::read(): Failed, compressed format \"%s\" is not supported by your Qt lib. OSType: \"%s\"", + format, nameFromOSType(icon.header.ostype).constData()); + } + } else if (qMin(icon.width, icon.height) > 0) { + switch (icon.depth) { + case ICNSEntry::DepthMono: + img = readLowDepthIcon<ICNSEntry::DepthMono>(icon, stream); + break; + case ICNSEntry::Depth4bit: + img = readLowDepthIcon<ICNSEntry::Depth4bit>(icon, stream); + break; + case ICNSEntry::Depth8bit: + img = readLowDepthIcon<ICNSEntry::Depth8bit>(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.header.ostype).constData()); + } + if (!img.isNull()) { + QImage alpha = readMask(getIconMask(icon), stream); + if (!alpha.isNull()) + img.setAlphaChannel(alpha); + } + } + *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(QImageIOHandler::ImageOption option) const +{ + return option == QImageIOHandler::SubType; +} + +QVariant QICNSHandler::option(QImageIOHandler::ImageOption option) const +{ + if (!supportsOption(option) || !ensureScanned()) + return QVariant(); + + if (option == QImageIOHandler::SubType) { + if (imageCount() > 0 && m_currentIconIndex <= imageCount()) + return nameFromOSType(m_icons.at(m_currentIconIndex).header.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<QICNSHandler *>(this); + that->m_state = that->scanDevice() ? ScanSuccess : ScanError; + } + + return m_state == ScanSuccess; +} + +void QICNSHandler::addEntry(const ICNSBlockHeader &header, quint32 imgDataOffset) +{ + ICNSEntry entry; + // Header: + entry.header.ostype = header.ostype; + entry.header.length = header.length; + // Image data: + entry.dataOffset = imgDataOffset; + entry.dataLength = header.length - ICNSBlockHeaderSize; + entry.dataIsRLE = false; + // Parse everything else and index this entry: + if (parseIconEntry(entry)) { + if ((entry.mask & ICNSEntry::IsMask) != 0) + m_masks << entry; + if ((entry.mask & ICNSEntry::IsIcon) != 0) + m_icons << entry; + } +} + +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 quint64 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; + + 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) + 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::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 + if (!device()->seek(blockDataOffset + blockDataLength)) + return false; + break; + } + addEntry(tocEntry, imgDataOffset); + 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 that is left behind + 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); + } else { + addEntry(blockHeader, blockDataOffset); + } + 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 &entry = m_masks.at(i); + if (entry.depth == targetDepth && entry.height == icon.height && entry.width == icon.width) + return entry; + } + return icon; +} + +QT_END_NAMESPACE + +#endif // QT_NO_DATASTREAM diff --git a/src/plugins/imageformats/icns/qicnshandler_p.h b/src/plugins/imageformats/icns/qicnshandler_p.h new file mode 100644 index 0000000000000000000000000000000000000000..0bfb76d369e0c07326d62e5ad6a8033f2b3af47c --- /dev/null +++ b/src/plugins/imageformats/icns/qicnshandler_p.h @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 Alex Char. +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QICNSHANDLER_P_H +#define QICNSHANDLER_P_H + +#include <QtGui/qimageiohandler.h> +#include <QtCore/qvector.h> + +#ifndef QT_NO_DATASTREAM + +#define MAKEOSTYPE(c0,c1,c2,c3) (((quint8)c0 << 24) | ((quint8)c1 << 16) | ((quint8)c2 << 8) | (quint8)c3) + +QT_BEGIN_NAMESPACE + +struct ICNSBlockHeader +{ + enum OS { + TypeIcns = MAKEOSTYPE('i', 'c', 'n', 's'), // Icns container magic + TypeToc = MAKEOSTYPE('T', 'O', 'C', ' '), // Table of contents + TypeIcnv = MAKEOSTYPE('i', 'c', 'n', 'V'), // Version of the icns tool + // Legacy: + TypeClut = MAKEOSTYPE('c', 'l', 'u', 't') // Color look-up table (pre-OS X resources) + }; + + quint32 ostype; + quint32 length; +}; + +struct ICNSEntry +{ + enum Group { + GroupUnknown = 0, + GroupMini = 'm', // "mini" (16x12) + GroupSmall = 's', // "small" (16x16) + GroupLarge = 'l', // "large" (32x32) + GroupHuge = 'h', // "huge" (48x48) + GroupThumbnail = 't', // "thumbnail" (128x128) + GroupPortable = 'p', // "portable"? (various sizes, png/jp2) + GroupCompressed = 'c', // "compressed"? (various sizes, png/jp2) + // Legacy icons: + GroupICON = 'N', // "ICON" (32x32) + }; + enum Depth { + DepthUnknown = 0, // Default for invalid ones + DepthMono = 1, + Depth4bit = 4, + Depth8bit = 8, + Depth32bit = 32 + }; + enum Mask { + MaskUnknown = 0x0, // Default for invalid ones + IsIcon = 0x1, // Plain icon without alpha + IsMask = 0x2, // The whole icon entry is alpha mask + IconPlusMask = IsMask | IsIcon // Plain icon and alpha mask (double size) + }; + + ICNSBlockHeader header; // Original block header + Group group; // ASCII character number pointing to a format + Depth depth; // Color depth or icon format number for compressed icons + Mask mask; // Flags for uncompressed, should be always IsIcon (0x1) for compressed + quint32 width; // For uncompressed icons only, zero for compressed ones for now + quint32 height; // For uncompressed icons only, zero for compressed ones fow now + quint32 dataLength; // header.length - sizeof(header) + quint32 dataOffset; // Offset from the initial position of the file/device + bool dataIsRLE; // 32bit raw icons may be in rle24 compressed state +}; + +class QICNSHandler : public QImageIOHandler +{ +public: + QICNSHandler(); + + bool canRead() const; + bool read(QImage *image); + bool write(const QImage &image); + + QByteArray name() const; + + bool supportsOption(ImageOption option) const; + QVariant option(ImageOption option) const; + + int imageCount() const; + bool jumpToImage(int imageNumber); + bool jumpToNextImage(); + + static bool canRead(QIODevice *device); + +private: + bool ensureScanned() const; + bool scanDevice(); + void addEntry(const ICNSBlockHeader &header, quint32 imgDataOffset); + const ICNSEntry &getIconMask(const ICNSEntry &icon) const; + +private: + enum ScanState { + ScanError = -1, + ScanNotScanned = 0, + ScanSuccess = 1, + }; + + int m_currentIconIndex; + QVector<ICNSEntry> m_icons; + QVector<ICNSEntry> m_masks; + ScanState m_state; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_DATASTREAM + +#endif /* QICNSHANDLER_P_H */ diff --git a/src/plugins/imageformats/imageformats.pro b/src/plugins/imageformats/imageformats.pro index ef9eb6fa973688841a8660c3d6250f6cd028e852..d3a0d3af0d034563619adda14f9678c158937a00 100644 --- a/src/plugins/imageformats/imageformats.pro +++ b/src/plugins/imageformats/imageformats.pro @@ -4,4 +4,5 @@ SUBDIRS = \ wbmp \ mng \ tiff \ - dds + dds \ + icns diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 6ac74e213f63a04c30679301fcee50876ac242c0..109f216ad891a83a138559ddf8e21e2b9748dc82 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -2,5 +2,7 @@ TEMPLATE = subdirs SUBDIRS = \ tga \ wbmp \ - dds + dds \ + icns + contains(QT_CONFIG, system-zlib): SUBDIRS += mng tiff diff --git a/tests/auto/icns/icns.pro b/tests/auto/icns/icns.pro new file mode 100644 index 0000000000000000000000000000000000000000..ef891b7c851c14270ab44e6bf8c75b1b716eccd3 --- /dev/null +++ b/tests/auto/icns/icns.pro @@ -0,0 +1,8 @@ +TARGET = tst_qicns + +QT = core gui testlib +CONFIG -= app_bundle +CONFIG += testcase + +SOURCES += tst_qicns.cpp +RESOURCES += $$PWD/../../shared/images/icns.qrc diff --git a/tests/auto/icns/tst_qicns.cpp b/tests/auto/icns/tst_qicns.cpp new file mode 100644 index 0000000000000000000000000000000000000000..261590e21af5fcefea2bf60d657ff75976ff5679 --- /dev/null +++ b/tests/auto/icns/tst_qicns.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 Alex Char. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the ICNS autotests in the Qt ImageFormats module. +** +** $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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QtGui/QtGui> + +class tst_qicns: public QObject +{ + Q_OBJECT + +private slots: + void readIcons_data(); + void readIcons(); + void writeIcons_data(); + void writeIcons(); +}; + +void tst_qicns::readIcons_data() +{ + QTest::addColumn<QString>("fileName"); + QTest::addColumn<QSize>("size"); + QTest::addColumn<int>("imageCount"); + QTest::addColumn<QByteArray>("format"); + + QTest::newRow("1") << QStringLiteral("test-png") << QSize(128, 128) << 7 << QByteArrayLiteral("png"); + QTest::newRow("2") << QStringLiteral("test-jp2") << QSize(128, 128) << 7 << QByteArrayLiteral("jp2"); + QTest::newRow("3") << QStringLiteral("test-32bit") << QSize(128, 128) << 4 << QByteArray(); + QTest::newRow("4") << QStringLiteral("test-legacy") << QSize(48, 48) << 12 << QByteArray(); +} + +void tst_qicns::readIcons() +{ + QFETCH(QString, fileName); + QFETCH(QSize, size); + QFETCH(int, imageCount); + QFETCH(QByteArray, format); + + if (!format.isEmpty() && !QImageReader::supportedImageFormats().contains(format)) + QSKIP("This test requires another image format plugin"); + const QString path = QStringLiteral(":/icns/") + fileName + QStringLiteral(".icns"); + QImageReader reader(path); + QVERIFY(reader.canRead()); + QCOMPARE(reader.imageCount(), imageCount); + + for (int i = 0; i < reader.imageCount(); ++i) { + QVERIFY2(reader.jumpToImage(i), qPrintable(reader.errorString())); + QImage image = reader.read(); + if (i == 0) + QCOMPARE(image.size(), size); + QVERIFY2(!image.isNull(), qPrintable(reader.errorString())); + } +} + +void tst_qicns::writeIcons_data() +{ + QTest::addColumn<QString>("fileName"); + QTest::addColumn<QSize>("size"); + + QTest::newRow("1") << QStringLiteral("test-write-16") << QSize(16, 16); + QTest::newRow("2") << QStringLiteral("test-write-32") << QSize(32, 32); + QTest::newRow("3") << QStringLiteral("test-write-64") << QSize(64, 64); + QTest::newRow("4") << QStringLiteral("test-write-128") << QSize(128, 128); + QTest::newRow("5") << QStringLiteral("test-write-512") << QSize(512, 512); + QTest::newRow("6") << QStringLiteral("test-write-1024") << QSize(1024, 1024); +} + +void tst_qicns::writeIcons() +{ + QTemporaryDir temp(QDir::tempPath() + QStringLiteral("/tst_qincs")); + QVERIFY2(temp.isValid(), "Unable to create temp dir."); + + QFETCH(QString, fileName); + QFETCH(QSize, size); + + const QString distPath = QStringLiteral("%1/%2.icns").arg(temp.path()).arg(fileName); + const QString sourcePath = QStringLiteral(":/icns/%1.png").arg(fileName); + + QImage image(sourcePath); + QVERIFY(!image.isNull()); + QVERIFY(image.size() == size); + + QImageWriter writer(distPath, QByteArrayLiteral("icns")); + QVERIFY2(writer.canWrite(), qPrintable(writer.errorString())); + QVERIFY2(writer.write(image), qPrintable(writer.errorString())); + + QVERIFY(image == QImage(distPath)); +} + +QTEST_MAIN(tst_qicns) +#include "tst_qicns.moc" diff --git a/tests/shared/images/icns.qrc b/tests/shared/images/icns.qrc new file mode 100644 index 0000000000000000000000000000000000000000..072b78c2fa53a45671cd10005b685458c85c3293 --- /dev/null +++ b/tests/shared/images/icns.qrc @@ -0,0 +1,15 @@ +<RCC> + <qresource prefix="/"> + <file>icns/test-png.icns</file> + <file>icns/test-jp2.icns</file> + <file>icns/test-32bit.icns</file> + <file>icns/test-legacy.icns</file> + <file>icns/test-write-16.png</file> + <file>icns/test-write-32.png</file> + <file>icns/test-write-64.png</file> + <file>icns/test-write-128.png</file> + <file>icns/test-write-256.png</file> + <file>icns/test-write-512.png</file> + <file>icns/test-write-1024.png</file> + </qresource> +</RCC> diff --git a/tests/shared/images/icns/test-32bit.icns b/tests/shared/images/icns/test-32bit.icns new file mode 100644 index 0000000000000000000000000000000000000000..be3921e362b84ed3e2951cfe1eabf49bb0e57c85 Binary files /dev/null and b/tests/shared/images/icns/test-32bit.icns differ diff --git a/tests/shared/images/icns/test-jp2.icns b/tests/shared/images/icns/test-jp2.icns new file mode 100644 index 0000000000000000000000000000000000000000..57125bd38cf0f792743a912773f832b3bf23260a Binary files /dev/null and b/tests/shared/images/icns/test-jp2.icns differ diff --git a/tests/shared/images/icns/test-legacy.icns b/tests/shared/images/icns/test-legacy.icns new file mode 100644 index 0000000000000000000000000000000000000000..fc66fac973fef9512cc11ee58c9fb6198f67a907 Binary files /dev/null and b/tests/shared/images/icns/test-legacy.icns differ diff --git a/tests/shared/images/icns/test-png.icns b/tests/shared/images/icns/test-png.icns new file mode 100644 index 0000000000000000000000000000000000000000..600bc67a075a989beff7aced0178ad4c25c219b6 Binary files /dev/null and b/tests/shared/images/icns/test-png.icns differ diff --git a/tests/shared/images/icns/test-write-1024.png b/tests/shared/images/icns/test-write-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..d62d14f545ebcdc109d8b4feb42946d3fbb30058 Binary files /dev/null and b/tests/shared/images/icns/test-write-1024.png differ diff --git a/tests/shared/images/icns/test-write-128.png b/tests/shared/images/icns/test-write-128.png new file mode 100644 index 0000000000000000000000000000000000000000..15709761f10e40d0da3c0516cd5417775239e5cd Binary files /dev/null and b/tests/shared/images/icns/test-write-128.png differ diff --git a/tests/shared/images/icns/test-write-16.png b/tests/shared/images/icns/test-write-16.png new file mode 100644 index 0000000000000000000000000000000000000000..a3b27e2efc26baeec253582dfcd45cf257399f7e Binary files /dev/null and b/tests/shared/images/icns/test-write-16.png differ diff --git a/tests/shared/images/icns/test-write-256.png b/tests/shared/images/icns/test-write-256.png new file mode 100644 index 0000000000000000000000000000000000000000..7a22292369ccbaaabd8435024d765a5ef2f9b14b Binary files /dev/null and b/tests/shared/images/icns/test-write-256.png differ diff --git a/tests/shared/images/icns/test-write-32.png b/tests/shared/images/icns/test-write-32.png new file mode 100644 index 0000000000000000000000000000000000000000..ecede6f5023ce0016f52681eee9d91e27b34edb3 Binary files /dev/null and b/tests/shared/images/icns/test-write-32.png differ diff --git a/tests/shared/images/icns/test-write-512.png b/tests/shared/images/icns/test-write-512.png new file mode 100644 index 0000000000000000000000000000000000000000..12ce0d2b6f1153648f983cbb7050fd758bc8490f Binary files /dev/null and b/tests/shared/images/icns/test-write-512.png differ diff --git a/tests/shared/images/icns/test-write-64.png b/tests/shared/images/icns/test-write-64.png new file mode 100644 index 0000000000000000000000000000000000000000..d11ccc10470f321a048640e302201e2f7779d83e Binary files /dev/null and b/tests/shared/images/icns/test-write-64.png differ