Add support for reading icon variants in ICNS plugin
authorAlex <prevedtest@gmail.com>
Wed, 15 Jan 2014 15:36:44 +0000 (19:36 +0400)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Wed, 26 Feb 2014 08:18:49 +0000 (09:18 +0100)
Change-Id: I68395ac4e9604d852405643710aa79585974b3e3
Reviewed-by: Ivan Komissarov <ABBAPOH@gmail.com>
Reviewed-by: Konstantin Ritt <ritt.ks@gmail.com>
Reviewed-by: Jake Petroules <jake.petroules@petroules.com>
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
src/plugins/imageformats/icns/qicnshandler.cpp
src/plugins/imageformats/icns/qicnshandler_p.h
tests/auto/icns/tst_qicns.cpp
tests/shared/images/icns.qrc
tests/shared/images/icns/test-variants.icns [new file with mode: 0644]

index b99c667..c6bff58 100644 (file)
@@ -372,7 +372,8 @@ static inline bool isIconCompressed(const ICNSEntry &icon)
 
 static inline bool isMaskSuitable(const ICNSEntry &mask, const ICNSEntry &icon, ICNSEntry::Depth target)
 {
-    return mask.depth == target && mask.height == icon.height && mask.width == icon.width;
+    return mask.variant == icon.variant && mask.depth == target
+            && mask.height == icon.height && mask.width == icon.width;
 }
 
 static inline QByteArray nameFromOSType(quint32 ostype)
@@ -813,6 +814,8 @@ QVariant QICNSHandler::option(ImageOption option) const
     if (option == SubType) {
         if (imageCount() > 0 && m_currentIconIndex <= imageCount()) {
             const ICNSEntry &icon = m_icons.at(m_currentIconIndex);
+            if (icon.variant != 0)
+                return nameFromOSType(icon.variant) + '-' + nameFromOSType(icon.ostype);
             return nameFromOSType(icon.ostype);
         }
     }
@@ -852,11 +855,12 @@ bool QICNSHandler::ensureScanned() const
     return m_state == ScanSuccess;
 }
 
-bool QICNSHandler::addEntry(const ICNSBlockHeader &header, qint64 imgDataOffset)
+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:
@@ -920,6 +924,37 @@ bool QICNSHandler::scanDevice()
             // 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) {
index 12c165b..747cb3e 100644 (file)
@@ -60,6 +60,11 @@ struct ICNSBlockHeader
         TypeIcnv = MAKEOSTYPE('i', 'c', 'n', 'V'), // Icon Composer version
         // Legacy:
         TypeClut = MAKEOSTYPE('c', 'l', 'u', 't'), // Color look-up table (pre-OS X resources)
+        TypeTile = MAKEOSTYPE('t', 'i', 'l', 'e'), // Container (icon variants)
+        TypeOver = MAKEOSTYPE('o', 'v', 'e', 'r'), // Container (icon variants)
+        TypeOpen = MAKEOSTYPE('o', 'p', 'e', 'n'), // Container (icon variants)
+        TypeDrop = MAKEOSTYPE('d', 'r', 'o', 'p'), // Container (icon variants)
+        TypeOdrp = MAKEOSTYPE('o', 'd', 'r', 'p'), // Container (icon variants)
     };
 
     quint32 ostype;
@@ -102,6 +107,7 @@ struct ICNSEntry
     };
 
     quint32 ostype;     // Real OSType
+    quint32 variant;    // Virtual OSType: a parent container, zero if parent is icns root
     Group group;        // ASCII character number
     quint32 width;      // For uncompressed icons only, zero for compressed ones for now
     quint32 height;     // For uncompressed icons only, zero for compressed ones fow now
@@ -112,7 +118,7 @@ struct ICNSEntry
     qint64 dataOffset;  // Offset from the initial position of the file/device
 
     ICNSEntry() :
-        ostype(0), group(GroupUnknown), width(0), height(0), depth(DepthUnknown),
+        ostype(0), variant(0), group(GroupUnknown), width(0), height(0), depth(DepthUnknown),
         flags(Unknown), dataFormat(FormatUnknown), dataLength(0), dataOffset(0)
     {
     }
@@ -142,7 +148,7 @@ public:
 private:
     bool ensureScanned() const;
     bool scanDevice();
-    bool addEntry(const ICNSBlockHeader &header, qint64 imgDataOffset);
+    bool addEntry(const ICNSBlockHeader &header, qint64 imgDataOffset, quint32 variant = 0);
     const ICNSEntry &getIconMask(const ICNSEntry &icon) const;
 
 private:
index 261590e..29ca3ee 100644 (file)
@@ -65,6 +65,7 @@ void tst_qicns::readIcons_data()
     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();
+    QTest::newRow("5") << QStringLiteral("test-variants") << QSize(128, 128) << 5 << QByteArrayLiteral("png");
 }
 
 void tst_qicns::readIcons()
index 072b78c..f87830d 100644 (file)
@@ -4,6 +4,7 @@
         <file>icns/test-jp2.icns</file>
         <file>icns/test-32bit.icns</file>
         <file>icns/test-legacy.icns</file>
+        <file>icns/test-variants.icns</file>
         <file>icns/test-write-16.png</file>
         <file>icns/test-write-32.png</file>
         <file>icns/test-write-64.png</file>
diff --git a/tests/shared/images/icns/test-variants.icns b/tests/shared/images/icns/test-variants.icns
new file mode 100644 (file)
index 0000000..e623279
Binary files /dev/null and b/tests/shared/images/icns/test-variants.icns differ