File indexing completed on 2024-05-12 08:17:48

0001 /*
0002  * Copyright (c) 2019 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
0003  *
0004  * This library is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU Lesser General Public
0006  * License as published by the Free Software Foundation; either
0007  * version 2.1 of the License, or (at your option) version 3, or any
0008  * later version accepted by the membership of KDE e.V. (or its
0009  * successor approved by the membership of KDE e.V.), which shall
0010  * act as a proxy defined in Section 6 of version 3 of the license.
0011  *
0012  * This library is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  * Lesser General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU Lesser General Public
0018  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0019  */
0020 
0021 #include "blendercreator.h"
0022 
0023 #include <QFile>
0024 #include <QImage>
0025 #include <QPointer>
0026 #include <QtEndian>
0027 
0028 #include <KCompressionDevice>
0029 #include <KPluginFactory>
0030 
0031 K_PLUGIN_CLASS_WITH_JSON(BlenderCreator, "blenderthumbnail.json")
0032 
0033 BlenderCreator::BlenderCreator(QObject *parent, const QVariantList &args)
0034     : KIO::ThumbnailCreator(parent, args)
0035 {
0036 }
0037 
0038 BlenderCreator::~BlenderCreator() = default;
0039 
0040 // For more info. see https://developer.blender.org/diffusion/B/browse/master/release/bin/blender-thumbnailer.py
0041 
0042 KIO::ThumbnailResult BlenderCreator::create(const KIO::ThumbnailRequest &request)
0043 {
0044     QFile file (request.url().toLocalFile());
0045     if(!file.open(QIODevice::ReadOnly)) {
0046         return KIO::ThumbnailResult::fail();
0047     }
0048 
0049     QDataStream blendStream;
0050     blendStream.setDevice(&file);
0051     // Blender has an option to save files with gzip compression. First check if we are dealing with such files.
0052     QPointer<KCompressionDevice> gzFile;
0053     if(file.peek(2).startsWith("\x1F\x8B")) { // gzip magic (each gzip member starts with ID1(0x1f) and ID2(0x8b))
0054         file.close();
0055         gzFile = new KCompressionDevice(request.url().toLocalFile(), KCompressionDevice::GZip);
0056         if (gzFile->open(QIODevice::ReadOnly)) {
0057             blendStream.setDevice(gzFile);
0058         } else {
0059             return KIO::ThumbnailResult::fail();
0060         }
0061     }
0062 
0063     // First to check is file header.
0064     // BLEND file header format
0065     // Reference      Content                                     Size
0066     // id             "BLENDER"                                    7
0067     // pointer-size   _ (underscore)(32 bit)/ - (minus)(64 bit)    1
0068     // endianness     v (little) / V (big)                         1
0069     // version        "248" = 2.48 etc.                            3
0070 
0071     // Example header: "BLENDER-v257"
0072 
0073     QByteArray head(12, '\0');
0074     blendStream.readRawData(head.data(), 12);
0075     if(!head.startsWith("BLENDER") || head.right(3).toInt() < 250 /*blender pre 2.5 had no thumbs*/) {
0076         blendStream.device()->close();
0077         return KIO::ThumbnailResult::fail();
0078     }
0079 
0080     // Next is file block. This we have to skip.
0081     // File block header format
0082     //    Reference      Content                                               Size
0083     // 1. id             "REND","TEST", etc.                                    4
0084     // 2. size           Total length of the data after the file-block-header   4
0085     // 3. old mem. addr  Mem. address.                                          pointer-size i.e, 4(32bit)/8(64bit)
0086     // 4. SDNA index     Index of SDNA struct                                   4
0087     // 5. count          No. of struct in file-block                            4
0088 
0089     const bool isLittleEndian = head[8] == 'v';
0090     auto toInt32 = [isLittleEndian](const QByteArray &bytes) {
0091         return isLittleEndian ? qFromLittleEndian<qint32>(bytes.constData())
0092                               : qFromBigEndian<qint32>(bytes.constData());
0093     };
0094 
0095     const bool is64Bit = head[7] == '-';
0096     const int fileBlockHeaderSize = is64Bit ? 24 : 20; // size of file block header fields 1 to 5
0097     QByteArray fileBlockHeader(fileBlockHeaderSize, '\0');
0098     qint32 fileBlockSize = 0;
0099     while (true) {
0100         const int read = blendStream.readRawData(fileBlockHeader.data(), fileBlockHeaderSize);
0101         if (read != fileBlockHeaderSize) {
0102             return KIO::ThumbnailResult::fail();
0103         }
0104         fileBlockSize = toInt32(fileBlockHeader.mid(4, 4)); // second header field
0105         // skip actual file-block data.
0106         if (fileBlockHeader.startsWith("REND")) {
0107             blendStream.skipRawData(fileBlockSize);
0108         } else {
0109             break;
0110         }
0111     }
0112 
0113     if(!fileBlockHeader.startsWith("TEST")) {
0114         blendStream.device()->close();
0115         return KIO::ThumbnailResult::fail();
0116     }
0117 
0118     // Now comes actual thumbnail image data.
0119     QByteArray xy(8, '\0');
0120     blendStream.readRawData(xy.data(), 8);
0121     const qint32 x = toInt32(xy.left(4));
0122     const qint32 y = toInt32(xy.right(4));
0123 
0124     qint32 imgSize = fileBlockSize - 8;
0125     if (imgSize != x * y * 4) {
0126         blendStream.device()->close();
0127         return KIO::ThumbnailResult::fail();
0128     }
0129 
0130     QByteArray imgBuffer(imgSize, '\0');
0131     blendStream.readRawData(imgBuffer.data(), imgSize);
0132     QImage thumbnail((const uchar*)imgBuffer.constData(), x, y, QImage::Format_ARGB32);
0133     if(request.targetSize().width() != 128) {
0134         thumbnail = thumbnail.scaledToWidth(request.targetSize().width(), Qt::SmoothTransformation);
0135     }
0136     if(request.targetSize().height() != 128) {
0137         thumbnail = thumbnail.scaledToHeight(request.targetSize().height(), Qt::SmoothTransformation);
0138     }
0139     thumbnail = thumbnail.rgbSwapped();
0140     thumbnail = thumbnail.mirrored();
0141     QImage img = thumbnail.convertToFormat(QImage::Format_ARGB32_Premultiplied);
0142 
0143     blendStream.device()->close();
0144     return !img.isNull() ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail();
0145 }
0146 
0147 #include "blendercreator.moc"