Changeset View
Changeset View
Standalone View
Standalone View
thumbnail/blendercreator.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | * Copyright (c) 2019 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com> | ||||
3 | * | ||||
4 | * This library is free software; you can redistribute it and/or | ||||
5 | * modify it under the terms of the GNU Lesser General Public | ||||
6 | * License as published by the Free Software Foundation; either | ||||
7 | * version 2.1 of the License, or (at your option) version 3, or any | ||||
8 | * later version accepted by the membership of KDE e.V. (or its | ||||
9 | * successor approved by the membership of KDE e.V.), which shall | ||||
10 | * act as a proxy defined in Section 6 of version 3 of the license. | ||||
11 | * | ||||
12 | * This library is distributed in the hope that it will be useful, | ||||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||
15 | * Lesser General Public License for more details. | ||||
16 | * | ||||
17 | * You should have received a copy of the GNU Lesser General Public | ||||
18 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||||
19 | */ | ||||
20 | | ||||
21 | #include "blendercreator.h" | ||||
22 | | ||||
23 | #include <assert.h> | ||||
24 | #include <zlib.h> | ||||
25 | | ||||
26 | #include <QFile> | ||||
27 | #include <QImage> | ||||
28 | #include <QPointer> | ||||
29 | #include <QDebug> | ||||
30 | | ||||
31 | #include <KCompressionDevice> | ||||
32 | | ||||
33 | extern "C" | ||||
34 | { | ||||
35 | Q_DECL_EXPORT ThumbCreator *new_creator() | ||||
36 | { | ||||
37 | return new BlenderCreator; | ||||
38 | } | ||||
39 | } | ||||
40 | | ||||
41 | BlenderCreator::BlenderCreator() = default; | ||||
42 | | ||||
43 | BlenderCreator::~BlenderCreator() = default; | ||||
44 | | ||||
45 | // For more info. see https://developer.blender.org/diffusion/B/browse/master/release/bin/blender-thumbnailer.py | ||||
46 | | ||||
47 | bool BlenderCreator::create(const QString &path, int width , int height, QImage &img) | ||||
48 | { | ||||
49 | QFile file (path); | ||||
50 | if(!file.open(QIODevice::ReadOnly)) { | ||||
51 | return false; | ||||
52 | } | ||||
53 | | ||||
54 | QDataStream blendStream; | ||||
55 | blendStream.setDevice(&file); | ||||
56 | QPointer<KCompressionDevice> gzFile; | ||||
57 | if(file.peek(2).startsWith("\x1F\x8B")) { // gzip magic (each gzip member starts with ID1(0x1f) and ID2(0x8b)) | ||||
bruns: I think this is already done by the calling code - the thumbnailer gets only called when the… | |||||
This part is required because blender can save files with gzip compression. Examples here : https://download.blender.org/demo/test/Demo_274.zip. For some of them properties dialog shows "Contents: application/gzip". chinmoyr: This part is required because blender can save files with gzip compression. Examples here… | |||||
58 | file.close(); | ||||
59 | gzFile = new KCompressionDevice(path, KCompressionDevice::GZip); | ||||
60 | if (gzFile->open(QIODevice::ReadOnly)) { | ||||
61 | blendStream.setDevice(gzFile); | ||||
62 | } | ||||
bruns: `} else {` ...? | |||||
63 | } | ||||
64 | | ||||
65 | // First to check is file header. | ||||
66 | // BLEND file header format | ||||
67 | // Reference Content Size | ||||
68 | // id "BLENDER" 7 | ||||
69 | // pointer-size _ (underscore)(32 bit)/ - (minus)(64 bit) 1 | ||||
70 | // endianness v (little) / V (big) 1 | ||||
71 | // version "248" = 2.48 etc. 3 | ||||
72 | | ||||
73 | // Example header: "BLENDER-257" | ||||
bruns: This is missing the 'v' / 'V' | |||||
74 | | ||||
75 | char head[12]; | ||||
76 | blendStream.readRawData(head, 12); | ||||
77 | const QByteArray headBytes = QByteArray::fromRawData(head, 12); | ||||
78 | if(!headBytes.startsWith("BLENDER") || headBytes.right(3).toInt() < 250 /*blender pre 2.5 had no thumbs*/) { | ||||
79 | blendStream.device()->close(); | ||||
80 | return false; | ||||
81 | } | ||||
82 | | ||||
83 | // Next is file block. This we have to skip. | ||||
84 | // File block header format | ||||
85 | // Reference Content Size | ||||
86 | // 1. id "REND","TEST", etc. 4 | ||||
87 | // 2. size Total length of the data after the file-block-header 4 | ||||
88 | // 3. old mem. addr Mem. address. pointer-size i.e, 4(32bit)/8(64bit) | ||||
89 | // 4. SDNA index Index of SDNA struct 4 | ||||
90 | // 5. count No. of struct in file-block 4 | ||||
91 | | ||||
92 | const bool isLittleEndian = head[8] == 'v'; | ||||
93 | if(isLittleEndian) { | ||||
94 | blendStream.setByteOrder(QDataStream::LittleEndian); | ||||
95 | } else { | ||||
96 | blendStream.setByteOrder(QDataStream::BigEndian); | ||||
97 | } | ||||
98 | const qint32 REND = isLittleEndian ? 1145980242 /*python int.from_bytes(b'REND', byteorder='little')*/ : 1380273732; | ||||
When you write these as hex, it becomes more obvious one is "REND" while the other is "DNER". Although ... bruns: When you write these as hex, it becomes more obvious one is "REND" while the other is "DNER". | |||||
99 | const qint32 TEST = isLittleEndian ? 1414743380 : 1413829460; | ||||
100 | const bool is64Bit = head[7] == '-'; | ||||
101 | const int fileBlockHeadSize = is64Bit ? 24 : 20; // sum(1 through 6) | ||||
bruns: Does this mean 'size of header fields 1 to 5'? | |||||
chinmoyr: yes | |||||
102 | const int remainingHeader = fileBlockHeadSize - 8; // sum(3 through 5) | ||||
103 | qint32 code = 0, length = 0; | ||||
104 | while (true) { | ||||
105 | blendStream >> code >> length; | ||||
... you can use readRawData(data, 4) here and just compare with 'REND'. Or even always read the whole header, saving you the skipRawData(remainingHeader), and gets rid of remainingHeader. Actually, using operator>> on raw data, i.e. anything not written using QDataStream is dangerous. There is no guarantee an int is serialized as just 4 bytes. bruns: ... you can use `readRawData(data, 4)` here and just compare with 'REND'.
Or even always read… | |||||
106 | if (code == REND) { | ||||
107 | // Already passed through 1 & 2. Next, to skip section 3-5 + actual file-block data. | ||||
108 | blendStream.skipRawData(remainingHeader + length); | ||||
109 | } else { | ||||
110 | break; | ||||
111 | } | ||||
112 | } | ||||
113 | | ||||
114 | if(code != TEST) { | ||||
115 | blendStream.device()->close(); | ||||
116 | return false; | ||||
117 | } | ||||
118 | // For "TEST" there's nothing after header. | ||||
119 | blendStream.skipRawData(remainingHeader); | ||||
120 | | ||||
121 | // Now comes actual thumbnail image data. | ||||
122 | quint32 x = 0, y = 0; | ||||
123 | blendStream >> x; | ||||
124 | blendStream >> y; | ||||
125 | | ||||
126 | length = length - 8; | ||||
127 | if (length != x * y * 4) { | ||||
128 | blendStream.device()->close(); | ||||
129 | return false; | ||||
130 | } | ||||
131 | | ||||
132 | char *imgBuffer = new char[length]; | ||||
133 | blendStream.readRawData(imgBuffer, length); | ||||
134 | abgr_to_argb(imgBuffer, length); | ||||
bruns: https://doc.qt.io/qt-5/qimage.html#rgbSwapped-1 | |||||
chinmoyr: I am trying to swap bgr to rgb here. | |||||
bruns: So does the code I linked to. No need to cook your own. | |||||
Ah, sorry. I misunderstood what was written in doc. It really does swap them. chinmoyr: Ah, sorry. I misunderstood what was written in doc. It really does swap them. | |||||
135 | QImage thumbnail((unsigned char*)imgBuffer, x, y, QImage::Format_ARGB32); | ||||
136 | if(width != 128) { | ||||
137 | thumbnail = thumbnail.scaledToWidth(width, Qt::SmoothTransformation); | ||||
138 | } | ||||
139 | if(height != 128) { | ||||
140 | thumbnail = thumbnail.scaledToHeight(height, Qt::SmoothTransformation); | ||||
141 | } | ||||
142 | thumbnail = thumbnail.mirrored(); | ||||
143 | img = thumbnail.convertToFormat(QImage::Format_ARGB32_Premultiplied); | ||||
144 | | ||||
145 | delete imgBuffer; | ||||
146 | blendStream.device()->close(); | ||||
147 | return true; | ||||
148 | } | ||||
149 | | ||||
150 | void BlenderCreator::abgr_to_argb(char *buf, int size) const | ||||
151 | { | ||||
152 | char tmp; | ||||
153 | for (int i=0; i < size; i+=4) { | ||||
154 | tmp = buf[i]; | ||||
155 | buf[i] = buf[i + 2]; | ||||
156 | buf[i + 2] = tmp; | ||||
157 | } | ||||
158 | } |
I think this is already done by the calling code - the thumbnailer gets only called when the mimetype matches.