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"