Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xrelease/bin/blender-thumbnailer.py193
-rw-r--r--source/blender/CMakeLists.txt5
-rw-r--r--source/blender/blendthumb/CMakeLists.txt64
-rw-r--r--source/blender/blendthumb/src/BlenderThumb.cpp321
-rw-r--r--source/blender/blendthumb/src/blender_thumbnailer.cc113
-rw-r--r--source/blender/blendthumb/src/blendthumb.hh65
-rw-r--r--source/blender/blendthumb/src/blendthumb_extract.cc257
-rw-r--r--source/blender/blendthumb/src/blendthumb_png.cc158
-rw-r--r--source/blender/blendthumb/src/blendthumb_win32.cc237
-rw-r--r--source/blender/blendthumb/src/blendthumb_win32.def (renamed from source/blender/blendthumb/src/BlendThumb.def)0
-rw-r--r--source/blender/blendthumb/src/blendthumb_win32.rc (renamed from source/blender/blendthumb/src/BlendThumb.rc)0
-rw-r--r--source/blender/blendthumb/src/blendthumb_win32_dll.cc (renamed from source/blender/blendthumb/src/Dll.cpp)0
-rw-r--r--source/creator/CMakeLists.txt5
13 files changed, 883 insertions, 535 deletions
diff --git a/release/bin/blender-thumbnailer.py b/release/bin/blender-thumbnailer.py
deleted file mode 100755
index e050a681ca0..00000000000
--- a/release/bin/blender-thumbnailer.py
+++ /dev/null
@@ -1,193 +0,0 @@
-#!/usr/bin/env python3
-
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# <pep8 compliant>
-
-"""
-Thumbnailer runs with python 2.7 and 3.x.
-To run automatically with a file manager such as Nautilus, save this file
-in a directory that is listed in PATH environment variable, and create
-blender.thumbnailer file in ${HOME}/.local/share/thumbnailers/ directory
-with the following contents:
-
-[Thumbnailer Entry]
-TryExec=blender-thumbnailer.py
-Exec=blender-thumbnailer.py %u %o
-MimeType=application/x-blender;
-"""
-
-import struct
-
-
-def open_wrapper_get():
- """ wrap OS specific read functionality here, fallback to 'open()'
- """
-
- class GFileWrapper:
- __slots__ = ("mode", "g_file")
-
- def __init__(self, url, mode='r'):
- self.mode = mode # used in gzip module
- self.g_file = Gio.File.parse_name(url).read(None)
-
- def read(self, size):
- return self.g_file.read_bytes(size, None).get_data()
-
- def seek(self, offset, whence=0):
- self.g_file.seek(offset, [1, 0, 2][whence], None)
- return self.g_file.tell()
-
- def tell(self):
- return self.g_file.tell()
-
- def close(self):
- self.g_file.close(None)
-
- def open_local_url(url, mode='r'):
- o = urlparse(url)
- if o.scheme == '':
- path = o.path
- elif o.scheme == 'file':
- path = unquote(o.path)
- else:
- raise(IOError('URL scheme "%s" needs gi.repository.Gio module' % o.scheme))
- return open(path, mode)
-
- try:
- from gi.repository import Gio
- return GFileWrapper
- except ImportError:
- try:
- # Python 3
- from urllib.parse import urlparse, unquote
- except ImportError:
- # Python 2
- from urlparse import urlparse
- from urllib import unquote
- return open_local_url
-
-
-def blend_extract_thumb(path):
- import os
- open_wrapper = open_wrapper_get()
-
- REND = b'REND'
- TEST = b'TEST'
-
- blendfile = open_wrapper(path, 'rb')
-
- head = blendfile.read(12)
-
- if head[0:2] == b'\x1f\x8b': # gzip magic
- import gzip
- blendfile.close()
- blendfile = gzip.GzipFile('', 'rb', 0, open_wrapper(path, 'rb'))
- head = blendfile.read(12)
-
- if not head.startswith(b'BLENDER'):
- blendfile.close()
- return None, 0, 0
-
- is_64_bit = (head[7] == b'-'[0])
-
- # true for PPC, false for X86
- is_big_endian = (head[8] == b'V'[0])
-
- # blender pre 2.5 had no thumbs
- if head[9:11] <= b'24':
- return None, 0, 0
-
- sizeof_bhead = 24 if is_64_bit else 20
- int_endian = '>i' if is_big_endian else '<i'
- int_endian_pair = int_endian + 'i'
-
- while True:
- bhead = blendfile.read(sizeof_bhead)
-
- if len(bhead) < sizeof_bhead:
- return None, 0, 0
-
- code = bhead[:4]
- length = struct.unpack(int_endian, bhead[4:8])[0] # 4 == sizeof(int)
-
- if code == REND:
- blendfile.seek(length, os.SEEK_CUR)
- else:
- break
-
- if code != TEST:
- return None, 0, 0
-
- try:
- x, y = struct.unpack(int_endian_pair, blendfile.read(8)) # 8 == sizeof(int) * 2
- except struct.error:
- return None, 0, 0
-
- length -= 8 # sizeof(int) * 2
-
- if length != x * y * 4:
- return None, 0, 0
-
- image_buffer = blendfile.read(length)
-
- if len(image_buffer) != length:
- return None, 0, 0
-
- return image_buffer, x, y
-
-
-def write_png(buf, width, height):
- import zlib
-
- # reverse the vertical line order and add null bytes at the start
- width_byte_4 = width * 4
- raw_data = b"".join(b'\x00' + buf[span:span + width_byte_4] for span in range((height - 1) * width * 4, -1, - width_byte_4))
-
- def png_pack(png_tag, data):
- chunk_head = png_tag + data
- return struct.pack("!I", len(data)) + chunk_head + struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head))
-
- return b"".join([
- b'\x89PNG\r\n\x1a\n',
- png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
- png_pack(b'IDAT', zlib.compress(raw_data, 9)),
- png_pack(b'IEND', b'')])
-
-
-def main():
- import sys
-
- if len(sys.argv) < 3:
- print("Expected 2 arguments <input.blend> <output.png>")
- else:
- file_in = sys.argv[-2]
-
- buf, width, height = blend_extract_thumb(file_in)
-
- if buf:
- file_out = sys.argv[-1]
-
- f = open(file_out, "wb")
- f.write(write_png(buf, width, height))
- f.close()
-
-
-if __name__ == '__main__':
- main()
diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt
index 84d31bccc53..0a494677d96 100644
--- a/source/blender/CMakeLists.txt
+++ b/source/blender/CMakeLists.txt
@@ -131,6 +131,7 @@ add_subdirectory(io)
add_subdirectory(functions)
add_subdirectory(makesdna)
add_subdirectory(makesrna)
+add_subdirectory(blendthumb)
if(WITH_COMPOSITOR)
add_subdirectory(compositor)
@@ -159,7 +160,3 @@ endif()
if(WITH_FREESTYLE)
add_subdirectory(freestyle)
endif()
-
-if(WIN32)
- add_subdirectory(blendthumb)
-endif()
diff --git a/source/blender/blendthumb/CMakeLists.txt b/source/blender/blendthumb/CMakeLists.txt
index b42ca284ecb..4bcd27082c0 100644
--- a/source/blender/blendthumb/CMakeLists.txt
+++ b/source/blender/blendthumb/CMakeLists.txt
@@ -19,23 +19,59 @@
# ***** END GPL LICENSE BLOCK *****
#-----------------------------------------------------------------------------
-include_directories(${ZLIB_INCLUDE_DIRS})
+# Shared Thumbnail Extraction Logic
+
+include_directories(
+ ../blenlib
+ ../makesdna
+ ../../../intern/guardedalloc
+)
+
+include_directories(
+ SYSTEM
+ ${ZLIB_INCLUDE_DIRS}
+)
set(SRC
- src/BlenderThumb.cpp
- src/BlendThumb.def
- src/BlendThumb.rc
- src/Dll.cpp
+ src/blendthumb.hh
+ src/blendthumb_extract.cc
+ src/blendthumb_png.cc
)
-string(APPEND CMAKE_SHARED_LINKER_FLAGS_DEBUG " /nodefaultlib:MSVCRT.lib")
+if(WIN32)
+ # -----------------------------------------------------------------------------
+ # Build `BlendThumb.dll`
-add_library(BlendThumb SHARED ${SRC})
-setup_platform_linker_flags(BlendThumb)
-target_link_libraries(BlendThumb ${ZLIB_LIBRARIES})
+ set(SRC_WIN32
+ src/blendthumb_win32.cc
+ src/blendthumb_win32.def
+ src/blendthumb_win32.rc
+ src/blendthumb_win32_dll.cc
+ )
-install(
- FILES $<TARGET_FILE:BlendThumb>
- COMPONENT Blender
- DESTINATION "."
-)
+ add_definitions(-DNOMINMAX)
+
+ add_library(BlendThumb SHARED ${SRC} ${SRC_WIN32})
+
+ target_link_libraries(BlendThumb bf_blenlib dbghelp.lib Version.lib)
+ set_target_properties(BlendThumb PROPERTIES LINK_FLAGS_DEBUG "/NODEFAULTLIB:msvcrt")
+
+ install(
+ FILES $<TARGET_FILE:BlendThumb>
+ COMPONENT Blender
+ DESTINATION "."
+ )
+else()
+ # -----------------------------------------------------------------------------
+ # Build `blender-thumbnailer` executable
+
+ add_executable(blender-thumbnailer ${SRC} src/blender_thumbnailer.cc)
+ target_link_libraries(blender-thumbnailer bf_blenlib)
+ target_link_libraries(blender-thumbnailer ${PTHREADS_LIBRARIES})
+
+ install(
+ FILES $<TARGET_FILE:blender-thumbnailer>
+ COMPONENT Blender
+ DESTINATION "."
+ )
+endif()
diff --git a/source/blender/blendthumb/src/BlenderThumb.cpp b/source/blender/blendthumb/src/BlenderThumb.cpp
deleted file mode 100644
index 939e7bbf67c..00000000000
--- a/source/blender/blendthumb/src/BlenderThumb.cpp
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include <new>
-#include <shlwapi.h>
-#include <thumbcache.h> // For IThumbnailProvider.
-
-#pragma comment(lib, "shlwapi.lib")
-
-// this thumbnail provider implements IInitializeWithStream to enable being hosted
-// in an isolated process for robustness
-
-class CBlendThumb : public IInitializeWithStream, public IThumbnailProvider {
- public:
- CBlendThumb() : _cRef(1), _pStream(NULL)
- {
- }
-
- virtual ~CBlendThumb()
- {
- if (_pStream) {
- _pStream->Release();
- }
- }
-
- // IUnknown
- IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
- {
- static const QITAB qit[] = {
- QITABENT(CBlendThumb, IInitializeWithStream),
- QITABENT(CBlendThumb, IThumbnailProvider),
- {0},
- };
- return QISearch(this, qit, riid, ppv);
- }
-
- IFACEMETHODIMP_(ULONG) AddRef()
- {
- return InterlockedIncrement(&_cRef);
- }
-
- IFACEMETHODIMP_(ULONG) Release()
- {
- ULONG cRef = InterlockedDecrement(&_cRef);
- if (!cRef) {
- delete this;
- }
- return cRef;
- }
-
- // IInitializeWithStream
- IFACEMETHODIMP Initialize(IStream *pStream, DWORD grfMode);
-
- // IThumbnailProvider
- IFACEMETHODIMP GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha);
-
- private:
- long _cRef;
- IStream *_pStream; // provided during initialization.
-};
-
-HRESULT CBlendThumb_CreateInstance(REFIID riid, void **ppv)
-{
- CBlendThumb *pNew = new (std::nothrow) CBlendThumb();
- HRESULT hr = pNew ? S_OK : E_OUTOFMEMORY;
- if (SUCCEEDED(hr)) {
- hr = pNew->QueryInterface(riid, ppv);
- pNew->Release();
- }
- return hr;
-}
-
-// IInitializeWithStream
-IFACEMETHODIMP CBlendThumb::Initialize(IStream *pStream, DWORD)
-{
- HRESULT hr = E_UNEXPECTED; // can only be inited once
- if (_pStream == NULL) {
- // take a reference to the stream if we have not been inited yet
- hr = pStream->QueryInterface(&_pStream);
- }
- return hr;
-}
-
-#include "Wincodec.h"
-#include <math.h>
-#include <zlib.h>
-const unsigned char gzip_magic[3] = {0x1f, 0x8b, 0x08};
-
-// IThumbnailProvider
-IFACEMETHODIMP CBlendThumb::GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha)
-{
- ULONG BytesRead;
- HRESULT hr = S_FALSE;
- LARGE_INTEGER SeekPos;
-
- // Compressed?
- unsigned char in_magic[3];
- _pStream->Read(&in_magic, 3, &BytesRead);
- bool gzipped = true;
- for (int i = 0; i < 3; i++)
- if (in_magic[i] != gzip_magic[i]) {
- gzipped = false;
- break;
- }
-
- if (gzipped) {
- // Zlib inflate
- z_stream stream;
- stream.zalloc = Z_NULL;
- stream.zfree = Z_NULL;
- stream.opaque = Z_NULL;
-
- // Get compressed file length
- SeekPos.QuadPart = 0;
- _pStream->Seek(SeekPos, STREAM_SEEK_END, NULL);
-
- // Get compressed and uncompressed size
- uLong source_size;
- uLongf dest_size;
- // SeekPos.QuadPart = -4; // last 4 bytes define size of uncompressed file
- // ULARGE_INTEGER Tell;
- //_pStream->Seek(SeekPos,STREAM_SEEK_END,&Tell);
- // source_size = (uLong)Tell.QuadPart + 4; // src
- //_pStream->Read(&dest_size,4,&BytesRead); // dest
- dest_size = 1024 * 70; // thumbnail is currently always inside the first 65KB...if it moves or
- // enlargens this line will have to change or go!
- source_size = (uLong)max(SeekPos.QuadPart, dest_size); // for safety, assume no compression
-
- // Input
- Bytef *src = new Bytef[source_size];
- stream.next_in = (Bytef *)src;
- stream.avail_in = (uInt)source_size;
-
- // Output
- Bytef *dest = new Bytef[dest_size];
- stream.next_out = (Bytef *)dest;
- stream.avail_out = dest_size;
-
- // IStream to src
- SeekPos.QuadPart = 0;
- _pStream->Seek(SeekPos, STREAM_SEEK_SET, NULL);
- _pStream->Read(src, source_size, &BytesRead);
-
- // Do the inflation
- int err;
- err = inflateInit2(&stream, 16); // 16 means "gzip"...nice!
- err = inflate(&stream, Z_FINISH);
- err = inflateEnd(&stream);
-
- // Replace the IStream, which is read-only
- _pStream->Release();
- _pStream = SHCreateMemStream(dest, dest_size);
-
- delete[] src;
- delete[] dest;
- }
-
- // Blender version, early out if sub 2.5
- SeekPos.QuadPart = 9;
- _pStream->Seek(SeekPos, STREAM_SEEK_SET, NULL);
- char version[4];
- version[3] = '\0';
- _pStream->Read(&version, 3, &BytesRead);
- if (BytesRead != 3) {
- return E_UNEXPECTED;
- }
- int iVersion = atoi(version);
- if (iVersion < 250) {
- return S_FALSE;
- }
-
- // 32 or 64 bit blend?
- SeekPos.QuadPart = 7;
- _pStream->Seek(SeekPos, STREAM_SEEK_SET, NULL);
-
- char _PointerSize;
- _pStream->Read(&_PointerSize, 1, &BytesRead);
-
- int PointerSize = _PointerSize == '_' ? 4 : 8;
- int HeaderSize = 16 + PointerSize;
-
- // Find and read thumbnail ("TEST") block
- SeekPos.QuadPart = 12;
- _pStream->Seek(SeekPos, STREAM_SEEK_SET, NULL);
- int BlockOffset = 12;
- while (_pStream) {
- // Scan current block
- char BlockName[5];
- BlockName[4] = '\0';
- int BlockSize = 0;
-
- if (_pStream->Read(BlockName, 4, &BytesRead) == S_OK &&
- _pStream->Read((void *)&BlockSize, 4, &BytesRead) == S_OK) {
- if (strcmp(BlockName, "TEST") != 0) {
- SeekPos.QuadPart = BlockOffset += HeaderSize + BlockSize;
- _pStream->Seek(SeekPos, STREAM_SEEK_SET, NULL);
- continue;
- }
- }
- else {
- break; // eof
- }
-
- // Found the block
- SeekPos.QuadPart = BlockOffset + HeaderSize;
- _pStream->Seek(SeekPos, STREAM_SEEK_SET, NULL);
-
- int width, height;
- _pStream->Read((char *)&width, 4, &BytesRead);
- _pStream->Read((char *)&height, 4, &BytesRead);
- BlockSize -= 8;
-
- // Isolate RGBA data
- char *pRGBA = new char[BlockSize];
- _pStream->Read(pRGBA, BlockSize, &BytesRead);
-
- if (BytesRead != (ULONG)BlockSize) {
- return E_UNEXPECTED;
- }
-
- // Convert to BGRA for Windows
- for (int i = 0; i < BlockSize; i += 4) {
-#define RED_BYTE pRGBA[i]
-#define BLUE_BYTE pRGBA[i + 2]
-
- char red = RED_BYTE;
- RED_BYTE = BLUE_BYTE;
- BLUE_BYTE = red;
- }
-
- // Flip vertically (Blender stores it upside-down)
- unsigned int LineSize = width * 4;
- char *FlippedImage = new char[BlockSize];
- for (int i = 0; i < height; i++) {
- if (0 != memcpy_s(&FlippedImage[(height - i - 1) * LineSize],
- LineSize,
- &pRGBA[i * LineSize],
- LineSize)) {
- return E_UNEXPECTED;
- }
- }
- delete[] pRGBA;
- pRGBA = FlippedImage;
-
- // Create image
- *phbmp = CreateBitmap(width, height, 1, 32, pRGBA);
- if (!*phbmp) {
- return E_FAIL;
- }
- *pdwAlpha = WTSAT_ARGB; // it's actually BGRA, not sure why this works
-
- // Scale down if required
- if ((unsigned)width > cx || (unsigned)height > cx) {
- float scale = 1.0f / (max(width, height) / (float)cx);
- LONG NewWidth = (LONG)(width * scale);
- LONG NewHeight = (LONG)(height * scale);
-
-#ifdef _DEBUG
-# if 0
- MessageBox(0, "Attach now", "Debugging", MB_OK);
-# endif
-#endif
- IWICImagingFactory *pImgFac;
- hr = CoCreateInstance(
- CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pImgFac));
-
- IWICBitmap *WICBmp;
- hr = pImgFac->CreateBitmapFromHBITMAP(*phbmp, 0, WICBitmapUseAlpha, &WICBmp);
-
- BITMAPINFO bmi = {};
- bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
- bmi.bmiHeader.biWidth = NewWidth;
- bmi.bmiHeader.biHeight = -NewHeight;
- bmi.bmiHeader.biPlanes = 1;
- bmi.bmiHeader.biBitCount = 32;
- bmi.bmiHeader.biCompression = BI_RGB;
-
- BYTE *pBits;
- HBITMAP ResizedHBmp = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void **)&pBits, NULL, 0);
- hr = ResizedHBmp ? S_OK : E_OUTOFMEMORY;
- if (SUCCEEDED(hr)) {
- IWICBitmapScaler *pIScaler;
- hr = pImgFac->CreateBitmapScaler(&pIScaler);
- hr = pIScaler->Initialize(WICBmp, NewWidth, NewHeight, WICBitmapInterpolationModeFant);
-
- WICRect rect = {0, 0, NewWidth, NewHeight};
- hr = pIScaler->CopyPixels(&rect, NewWidth * 4, NewWidth * NewHeight * 4, pBits);
-
- if (SUCCEEDED(hr)) {
- DeleteObject(*phbmp);
- *phbmp = ResizedHBmp;
- }
- else {
- DeleteObject(ResizedHBmp);
- }
-
- pIScaler->Release();
- }
- WICBmp->Release();
- pImgFac->Release();
- }
- else {
- hr = S_OK;
- }
- break;
- }
- return hr;
-}
diff --git a/source/blender/blendthumb/src/blender_thumbnailer.cc b/source/blender/blendthumb/src/blender_thumbnailer.cc
new file mode 100644
index 00000000000..8dd9d5c0c0a
--- /dev/null
+++ b/source/blender/blendthumb/src/blender_thumbnailer.cc
@@ -0,0 +1,113 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup blendthumb
+ *
+ * This file defines the thumbnail generation command (typically used on UNIX).
+ *
+ * To run automatically with a file manager such as Nautilus, save this file
+ * in a directory that is listed in PATH environment variable, and create
+ * `blender.thumbnailer` file in `${HOME}/.local/share/thumbnailers/` directory
+ * with the following contents:
+ *
+ * \code{.txt}
+ * [Thumbnailer Entry]
+ * TryExec=blender-thumbnailer
+ * Exec=blender-thumbnailer %u %o
+ * MimeType=application/x-blender;
+ * \endcode
+ */
+
+#include <fstream>
+#include <optional>
+
+#include <fcntl.h>
+#ifndef WIN32
+# include <unistd.h> /* For read close. */
+#else
+# include "BLI_winstuff.h"
+# include "winsock2.h"
+# include <io.h> /* For open close read. */
+#endif
+
+#include "BLI_fileops.h"
+#include "BLI_filereader.h"
+#include "BLI_vector.hh"
+
+#include "blendthumb.hh"
+
+/**
+ * This function opens .blend file from src_blend, extracts thumbnail from file if there is one,
+ * and writes `.png` image into `dst_png`.
+ * Returns exit code (0 if successful).
+ */
+static eThumbStatus extract_png_from_blend_file(const char *src_blend, const char *dst_png)
+{
+ eThumbStatus err;
+
+ /* Open source file `src_blend`. */
+ const int src_file = BLI_open(src_blend, O_BINARY | O_RDONLY, 0);
+ if (src_file == -1) {
+ return BT_FILE_ERR;
+ }
+
+ /* Thumbnail reading is responsible for freeing `file` and closing `src_file`. */
+ FileReader *file = BLI_filereader_new_file(src_file);
+ if (file == nullptr) {
+ close(src_file);
+ return BT_FILE_ERR;
+ }
+
+ /* Extract thumbnail from file. */
+ Thumbnail thumb;
+ err = blendthumb_create_thumb_from_file(file, &thumb);
+ if (err != BT_OK) {
+ return err;
+ }
+
+ /* Write thumbnail to `dst_png`. */
+ const int dst_file = BLI_open(dst_png, O_BINARY | O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (dst_file == -1) {
+ return BT_FILE_ERR;
+ }
+
+ std::optional<blender::Vector<uint8_t>> png_buf_opt = blendthumb_create_png_data_from_thumb(
+ &thumb);
+ if (png_buf_opt == std::nullopt) {
+ err = BT_ERROR;
+ }
+ else {
+ blender::Vector<uint8_t> png_buf = *png_buf_opt;
+ err = (write(dst_file, png_buf.data(), png_buf.size()) == png_buf.size()) ? BT_OK :
+ BT_FILE_ERR;
+ }
+ close(dst_file);
+
+ return err;
+}
+
+int main(int argc, char *argv[])
+{
+ if (argc < 3) {
+ std::cerr << "Usage: blender-thumbnailer <input.blend> <output.png>" << std::endl;
+ return -1;
+ }
+
+ eThumbStatus ret = extract_png_from_blend_file(argv[1], argv[2]);
+
+ return (int)ret;
+}
diff --git a/source/blender/blendthumb/src/blendthumb.hh b/source/blender/blendthumb/src/blendthumb.hh
new file mode 100644
index 00000000000..c029a1766d6
--- /dev/null
+++ b/source/blender/blendthumb/src/blendthumb.hh
@@ -0,0 +1,65 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2008-2021 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup blendthumb
+ *
+ * Shared thumbnail extraction logic.
+ *
+ * Used for both MS-Windows DLL and Unix command line.
+ */
+
+#pragma once
+
+#include <optional>
+
+#include "BLI_array.hh"
+#include "BLI_vector.hh"
+
+struct FileReader;
+
+struct Thumbnail {
+ blender::Array<uint8_t> data;
+ int width;
+ int height;
+};
+
+enum eThumbStatus {
+ BT_OK = 0,
+ BT_FILE_ERR = 1,
+ BT_COMPRES_ERR = 2,
+ BT_DECOMPRESS_ERR = 3,
+ BT_INVALID_FILE = 4,
+ BT_EARLY_VERSION = 5,
+ BT_INVALID_THUMB = 6,
+ BT_ERROR = 9
+};
+
+std::optional<blender::Vector<uint8_t>> blendthumb_create_png_data_from_thumb(
+ const Thumbnail *thumb);
+eThumbStatus blendthumb_create_thumb_from_file(struct FileReader *rawfile, Thumbnail *thumb);
+
+/* INTEGER CODES */
+#ifdef __BIG_ENDIAN__
+/* Big Endian */
+# define MAKE_ID(a, b, c, d) ((int)(a) << 24 | (int)(b) << 16 | (c) << 8 | (d))
+#else
+/* Little Endian */
+# define MAKE_ID(a, b, c, d) ((int)(d) << 24 | (int)(c) << 16 | (b) << 8 | (a))
+#endif
diff --git a/source/blender/blendthumb/src/blendthumb_extract.cc b/source/blender/blendthumb/src/blendthumb_extract.cc
new file mode 100644
index 00000000000..99b13c89994
--- /dev/null
+++ b/source/blender/blendthumb/src/blendthumb_extract.cc
@@ -0,0 +1,257 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2008 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup blendthumb
+ *
+ * Expose #blendthumb_create_thumb_from_file that creates the PNG data
+ * but does not write it to a file.
+ */
+
+#include <cstring>
+
+#include "BLI_alloca.h"
+#include "BLI_endian_defines.h"
+#include "BLI_endian_switch.h"
+#include "BLI_fileops.h"
+#include "BLI_filereader.h"
+#include "BLI_string.h"
+
+#include "blendthumb.hh"
+
+static bool blend_header_check_magic(const char header[12])
+{
+ /* Check magic string at start of file. */
+ if (!BLI_str_startswith(header, "BLENDER")) {
+ return false;
+ }
+ /* Check pointer size and endianness indicators. */
+ if (((header[7] != '_') && (header[7] != '-')) || ((header[8] != 'v') && (header[8] != 'V'))) {
+ return false;
+ }
+ /* Check version number. */
+ if (!isdigit(header[9]) || !isdigit(header[10]) || !isdigit(header[11])) {
+ return false;
+ }
+ return true;
+}
+
+static bool blend_header_is_version_valid(const char header[12])
+{
+ /* Thumbnails are only in files with version >= 2.50 */
+ char num[4];
+ memcpy(num, header + 9, 3);
+ num[3] = 0;
+ return atoi(num) >= 250;
+}
+
+static int blend_header_pointer_size(const char header[12])
+{
+ return header[7] == '_' ? 4 : 8;
+}
+
+static bool blend_header_is_endian_switch_needed(const char header[12])
+{
+ return (((header[8] == 'v') ? L_ENDIAN : B_ENDIAN) != ENDIAN_ORDER);
+}
+
+static void thumb_data_vertical_flip(Thumbnail *thumb)
+{
+ uint32_t *rect = (uint32_t *)thumb->data.data();
+ int x = thumb->width, y = thumb->height;
+ uint32_t *top = rect;
+ uint32_t *bottom = top + ((y - 1) * x);
+ uint32_t *line = (uint32_t *)malloc(x * sizeof(uint32_t));
+
+ y >>= 1;
+ for (; y > 0; y--) {
+ memcpy(line, top, x * sizeof(uint32_t));
+ memcpy(top, bottom, x * sizeof(uint32_t));
+ memcpy(bottom, line, x * sizeof(uint32_t));
+ bottom -= x;
+ top += x;
+ }
+ free(line);
+}
+
+static int32_t bytes_to_native_i32(const uint8_t bytes[4], bool endian_switch)
+{
+ int32_t data;
+ memcpy(&data, bytes, 4);
+ if (endian_switch) {
+ BLI_endian_switch_int32(&data);
+ }
+ return data;
+}
+
+static bool file_read(FileReader *file, uint8_t *buf, size_t buf_len)
+{
+ return (file->read(file, buf, buf_len) == buf_len);
+}
+
+static bool file_seek(FileReader *file, size_t len)
+{
+ if (file->seek != nullptr) {
+ if (file->seek(file, len, SEEK_CUR) == -1) {
+ return false;
+ }
+ return true;
+ }
+
+ /* File doesn't support seeking (e.g. gzip), so read and discard in chunks. */
+ constexpr size_t dummy_data_size = 4096;
+ blender::Array<char> dummy_data(dummy_data_size);
+ while (len > 0) {
+ const size_t len_chunk = std::min(len, dummy_data_size);
+ if ((size_t)file->read(file, dummy_data.data(), len_chunk) != len_chunk) {
+ return false;
+ }
+ len -= len_chunk;
+ }
+ return true;
+}
+
+static eThumbStatus blendthumb_extract_from_file_impl(FileReader *file,
+ Thumbnail *thumb,
+ const size_t bhead_size,
+ const bool endian_switch)
+{
+ /* Iterate over file blocks until we find the thumbnail or run out of data. */
+ uint8_t *bhead_data = (uint8_t *)BLI_array_alloca(bhead_data, bhead_size);
+ while (file_read(file, bhead_data, bhead_size)) {
+ /* Parse type and size from `BHead`. */
+ const int32_t block_size = bytes_to_native_i32(&bhead_data[4], endian_switch);
+
+ /* We're looking for the thumbnail, so skip any other block. */
+ switch (*((int32_t *)bhead_data)) {
+ case MAKE_ID('T', 'E', 'S', 'T'): {
+ uint8_t shape[8];
+ if (!file_read(file, shape, sizeof(shape))) {
+ return BT_INVALID_THUMB;
+ }
+ thumb->width = bytes_to_native_i32(&shape[0], endian_switch);
+ thumb->height = bytes_to_native_i32(&shape[4], endian_switch);
+
+ /* Verify that image dimensions and data size make sense. */
+ size_t data_size = block_size - 8;
+ const size_t expected_size = thumb->width * thumb->height * 4;
+ if (thumb->width < 0 || thumb->height < 0 || data_size != expected_size) {
+ return BT_INVALID_THUMB;
+ }
+
+ thumb->data = blender::Array<uint8_t>(data_size);
+ if (!file_read(file, thumb->data.data(), data_size)) {
+ return BT_INVALID_THUMB;
+ }
+ return BT_OK;
+ }
+ case MAKE_ID('R', 'E', 'N', 'D'): {
+ if (!file_seek(file, block_size)) {
+ return BT_INVALID_THUMB;
+ }
+ /* Check the next block. */
+ break;
+ }
+ default: {
+ /* Early exit if there are no `TEST` or `REND` blocks.
+ * This saves scanning the entire blend file which could be slow. */
+ return BT_INVALID_THUMB;
+ }
+ }
+ }
+
+ return BT_INVALID_THUMB;
+}
+
+/**
+ * This function extracts the thumbnail from the .blend file into thumb.
+ * Returns #BT_OK for success and the relevant error code otherwise.
+ */
+eThumbStatus blendthumb_create_thumb_from_file(FileReader *rawfile, Thumbnail *thumb)
+{
+ /* Read header in order to identify file type. */
+ char header[12];
+ if (rawfile->read(rawfile, header, sizeof(header)) != sizeof(header)) {
+ rawfile->close(rawfile);
+ return BT_ERROR;
+ }
+
+ /* Rewind the file after reading the header. */
+ rawfile->seek(rawfile, 0, SEEK_SET);
+
+ /* Try to identify the file type from the header. */
+ FileReader *file = nullptr;
+ if (BLI_str_startswith(header, "BLENDER")) {
+ file = rawfile;
+ rawfile = nullptr;
+ }
+ else if (BLI_file_magic_is_gzip(header)) {
+ file = BLI_filereader_new_gzip(rawfile);
+ if (file != nullptr) {
+ rawfile = nullptr; /* The Gzip #FileReader takes ownership of raw-file. */
+ }
+ }
+ else if (BLI_file_magic_is_zstd(header)) {
+ file = BLI_filereader_new_zstd(rawfile);
+ if (file != nullptr) {
+ rawfile = nullptr; /* The Zstd #FileReader takes ownership of raw-file. */
+ }
+ }
+
+ /* Clean up rawfile if it wasn't taken over. */
+ if (rawfile != nullptr) {
+ rawfile->close(rawfile);
+ }
+
+ if (file == nullptr) {
+ return BT_ERROR;
+ }
+
+ /* Re-read header in case we had compression. */
+ if (file->read(file, header, sizeof(header)) != sizeof(header)) {
+ file->close(file);
+ return BT_ERROR;
+ }
+
+ /* Check if the header format is valid for a .blend file. */
+ if (!blend_header_check_magic(header)) {
+ file->close(file);
+ return BT_INVALID_FILE;
+ }
+
+ /* Check if the file is new enough to contain a thumbnail. */
+ if (!blend_header_is_version_valid(header)) {
+ file->close(file);
+ return BT_EARLY_VERSION;
+ }
+
+ /* Depending on where it was saved, the file can use different pointer size or endianness. */
+ int bhead_size = 16 + blend_header_pointer_size(header);
+ const bool endian_switch = blend_header_is_endian_switch_needed(header);
+
+ /* Read the thumbnail. */
+ eThumbStatus err = blendthumb_extract_from_file_impl(file, thumb, bhead_size, endian_switch);
+ file->close(file);
+ if (err != BT_OK) {
+ return err;
+ }
+
+ thumb_data_vertical_flip(thumb);
+ return BT_OK;
+}
diff --git a/source/blender/blendthumb/src/blendthumb_png.cc b/source/blender/blendthumb/src/blendthumb_png.cc
new file mode 100644
index 00000000000..d8156150078
--- /dev/null
+++ b/source/blender/blendthumb/src/blendthumb_png.cc
@@ -0,0 +1,158 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2008 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup blendthumb
+ *
+ * Expose #blendthumb_create_png_data_from_thumb that creates the PNG data
+ * but does not write it to a file.
+ */
+
+#include <cstring>
+#include <optional>
+#include <zlib.h>
+
+#include "blendthumb.hh"
+
+#include "BLI_endian_defines.h"
+#include "BLI_endian_switch.h"
+#include "BLI_vector.hh"
+
+static void png_extend_native_int32(blender::Vector<uint8_t> &output, int32_t data)
+{
+ if (ENDIAN_ORDER == L_ENDIAN) {
+ BLI_endian_switch_int32(&data);
+ }
+ output.extend_unchecked(blender::Span((uint8_t *)&data, 4));
+}
+
+/** The number of bytes each chunk uses on top of the data that's written. */
+#define PNG_CHUNK_EXTRA 12
+
+static void png_chunk_create(blender::Vector<uint8_t> &output,
+ const uint32_t tag,
+ const blender::Vector<uint8_t> &data)
+{
+ uint32_t crc = crc32(0, nullptr, 0);
+ crc = crc32(crc, (uint8_t *)&tag, sizeof(tag));
+ crc = crc32(crc, (uint8_t *)data.data(), data.size());
+
+ png_extend_native_int32(output, data.size());
+ output.extend_unchecked(blender::Span((uint8_t *)&tag, sizeof(tag)));
+ output.extend_unchecked(data);
+ png_extend_native_int32(output, crc);
+}
+
+static blender::Vector<uint8_t> filtered_rows_from_thumb(const Thumbnail *thumb)
+{
+ /* In the image data sent to the compression step, each scan-line is preceded by a filter type
+ * byte containing the numeric code of the filter algorithm used for that scan-line. */
+ const size_t line_size = thumb->width * 4;
+ blender::Vector<uint8_t> filtered{};
+ size_t final_size = thumb->height * (line_size + 1);
+ filtered.reserve(final_size);
+ for (int i = 0; i < thumb->height; i++) {
+ filtered.append_unchecked(0x00);
+ filtered.extend_unchecked(blender::Span(&thumb->data[i * line_size], line_size));
+ }
+ BLI_assert(final_size == filtered.size());
+ return filtered;
+}
+
+static std::optional<blender::Vector<uint8_t>> zlib_compress(const blender::Vector<uint8_t> &data)
+{
+ unsigned long uncompressed_size = data.size();
+ uLongf compressed_size = compressBound(uncompressed_size);
+
+ blender::Vector<uint8_t> compressed(compressed_size, 0x00);
+
+ int return_value = compress2((uchar *)compressed.data(),
+ &compressed_size,
+ (uchar *)data.data(),
+ uncompressed_size,
+ Z_NO_COMPRESSION);
+ if (return_value != Z_OK) {
+ /* Something went wrong with compression of data. */
+ return std::nullopt;
+ }
+ compressed.resize(compressed_size);
+ return compressed;
+}
+
+std::optional<blender::Vector<uint8_t>> blendthumb_create_png_data_from_thumb(
+ const Thumbnail *thumb)
+{
+ if (thumb->data.is_empty()) {
+ return std::nullopt;
+ }
+
+ /* Create `IDAT` chunk data. */
+ blender::Vector<uint8_t> image_data{};
+ {
+ auto image_data_opt = zlib_compress(filtered_rows_from_thumb(thumb));
+ if (image_data_opt == std::nullopt) {
+ return std::nullopt;
+ }
+ image_data = *image_data_opt;
+ }
+
+ /* Create the IHDR chunk data. */
+ blender::Vector<uint8_t> ihdr_data{};
+ {
+ const size_t ihdr_data_final_size = 4 + 4 + 5;
+ ihdr_data.reserve(ihdr_data_final_size);
+ png_extend_native_int32(ihdr_data, thumb->width);
+ png_extend_native_int32(ihdr_data, thumb->height);
+ ihdr_data.extend_unchecked({
+ 0x08, /* Bit Depth. */
+ 0x06, /* Color Type. */
+ 0x00, /* Compression method. */
+ 0x00, /* Filter method. */
+ 0x00, /* Interlace method. */
+ });
+ BLI_assert((size_t)ihdr_data.size() == ihdr_data_final_size);
+ }
+
+ /* Join it all together to create a PNG image. */
+ blender::Vector<uint8_t> png_buf{};
+ {
+ const size_t png_buf_final_size = (
+ /* Header. */
+ 8 +
+ /* `IHDR` chunk. */
+ (ihdr_data.size() + PNG_CHUNK_EXTRA) +
+ /* `IDAT` chunk. */
+ (image_data.size() + PNG_CHUNK_EXTRA) +
+ /* `IEND` chunk. */
+ PNG_CHUNK_EXTRA);
+
+ png_buf.reserve(png_buf_final_size);
+
+ /* This is the standard PNG file header. Every PNG file starts with it. */
+ png_buf.extend_unchecked({0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A});
+
+ png_chunk_create(png_buf, MAKE_ID('I', 'H', 'D', 'R'), ihdr_data);
+ png_chunk_create(png_buf, MAKE_ID('I', 'D', 'A', 'T'), image_data);
+ png_chunk_create(png_buf, MAKE_ID('I', 'E', 'N', 'D'), {});
+
+ BLI_assert((size_t)png_buf.size() == png_buf_final_size);
+ }
+
+ return png_buf;
+}
diff --git a/source/blender/blendthumb/src/blendthumb_win32.cc b/source/blender/blendthumb/src/blendthumb_win32.cc
new file mode 100644
index 00000000000..d757bb1c97e
--- /dev/null
+++ b/source/blender/blendthumb/src/blendthumb_win32.cc
@@ -0,0 +1,237 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup blendthumb
+ *
+ * Thumbnail from Blend file extraction for MS-Windows.
+ */
+
+#include <math.h>
+#include <new>
+#include <shlwapi.h>
+#include <string>
+#include <thumbcache.h> /* for #IThumbnailProvider */
+
+#include "Wincodec.h"
+
+#include "blendthumb.hh"
+
+#include "BLI_filereader.h"
+
+#pragma comment(lib, "shlwapi.lib")
+
+/**
+ * This thumbnail provider implements #IInitializeWithStream to enable being hosted
+ * in an isolated process for robustness.
+ */
+class CBlendThumb : public IInitializeWithStream, public IThumbnailProvider {
+ public:
+ CBlendThumb() : _cRef(1), _pStream(NULL)
+ {
+ }
+
+ virtual ~CBlendThumb()
+ {
+ if (_pStream) {
+ _pStream->Release();
+ }
+ }
+
+ IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
+ {
+ static const QITAB qit[] = {
+ QITABENT(CBlendThumb, IInitializeWithStream),
+ QITABENT(CBlendThumb, IThumbnailProvider),
+ {0},
+ };
+ return QISearch(this, qit, riid, ppv);
+ }
+
+ IFACEMETHODIMP_(ULONG) AddRef()
+ {
+ return InterlockedIncrement(&_cRef);
+ }
+
+ IFACEMETHODIMP_(ULONG) Release()
+ {
+ ULONG cRef = InterlockedDecrement(&_cRef);
+ if (!cRef) {
+ delete this;
+ }
+ return cRef;
+ }
+
+ /** IInitializeWithStream */
+ IFACEMETHODIMP Initialize(IStream *pStream, DWORD grfMode);
+
+ /** IThumbnailProvider */
+ IFACEMETHODIMP GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha);
+
+ private:
+ long _cRef;
+ IStream *_pStream; /* provided in Initialize(). */
+};
+
+HRESULT CBlendThumb_CreateInstance(REFIID riid, void **ppv)
+{
+ CBlendThumb *pNew = new (std::nothrow) CBlendThumb();
+ HRESULT hr = pNew ? S_OK : E_OUTOFMEMORY;
+ if (SUCCEEDED(hr)) {
+ hr = pNew->QueryInterface(riid, ppv);
+ pNew->Release();
+ }
+ return hr;
+}
+
+IFACEMETHODIMP CBlendThumb::Initialize(IStream *pStream, DWORD)
+{
+ if (_pStream != NULL) {
+ /* Can only be initialized once. */
+ return E_UNEXPECTED;
+ }
+ /* Take a reference to the stream. */
+ return pStream->QueryInterface(&_pStream);
+}
+
+/**
+ * #FileReader compatible wrapper around the Windows stream that gives access to the .blend file.
+ */
+typedef struct {
+ FileReader reader;
+
+ IStream *_pStream;
+} StreamReader;
+
+static ssize_t stream_read(FileReader *reader, void *buffer, size_t size)
+{
+ StreamReader *stream = (StreamReader *)reader;
+
+ ULONG readsize;
+ stream->_pStream->Read(buffer, size, &readsize);
+ stream->reader.offset += readsize;
+
+ return (ssize_t)readsize;
+}
+
+static off64_t stream_seek(FileReader *reader, off64_t offset, int whence)
+{
+ StreamReader *stream = (StreamReader *)reader;
+
+ DWORD origin = STREAM_SEEK_SET;
+ switch (whence) {
+ case SEEK_CUR:
+ origin = STREAM_SEEK_CUR;
+ break;
+ case SEEK_END:
+ origin = STREAM_SEEK_END;
+ break;
+ }
+ LARGE_INTEGER offsetI;
+ offsetI.QuadPart = offset;
+ ULARGE_INTEGER newPos;
+ stream->_pStream->Seek(offsetI, origin, &newPos);
+ stream->reader.offset = newPos.QuadPart;
+
+ return stream->reader.offset;
+}
+
+static void stream_close(FileReader *reader)
+{
+ StreamReader *stream = (StreamReader *)reader;
+ delete stream;
+}
+
+IFACEMETHODIMP CBlendThumb::GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha)
+{
+ HRESULT hr = S_FALSE;
+
+ StreamReader *file = new StreamReader;
+ file->reader.read = stream_read;
+ file->reader.seek = stream_seek;
+ file->reader.close = stream_close;
+ file->reader.offset = 0;
+ file->_pStream = _pStream;
+
+ file->reader.seek(&file->reader, 0, SEEK_SET);
+
+ /* Extract thumbnail from stream. */
+ Thumbnail thumb;
+ if (blendthumb_create_thumb_from_file(&file->reader, &thumb) != BT_OK) {
+ return S_FALSE;
+ }
+
+ /* Convert to BGRA for Windows. */
+ for (int i = 0; i < thumb.width * thumb.height; i++) {
+ std::swap(thumb.data[4 * i], thumb.data[4 * i + 2]);
+ }
+
+ *phbmp = CreateBitmap(thumb.width, thumb.height, 1, 32, thumb.data.data());
+ if (!*phbmp) {
+ return E_FAIL;
+ }
+ *pdwAlpha = WTSAT_ARGB;
+
+ /* Scale down the thumbnail if required. */
+ if ((unsigned)thumb.width > cx || (unsigned)thumb.height > cx) {
+ float scale = 1.0f / (std::max(thumb.width, thumb.height) / (float)cx);
+ LONG NewWidth = (LONG)(thumb.width * scale);
+ LONG NewHeight = (LONG)(thumb.height * scale);
+
+ IWICImagingFactory *pImgFac;
+ hr = CoCreateInstance(
+ CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pImgFac));
+
+ IWICBitmap *WICBmp;
+ hr = pImgFac->CreateBitmapFromHBITMAP(*phbmp, 0, WICBitmapUseAlpha, &WICBmp);
+
+ BITMAPINFO bmi = {};
+ bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
+ bmi.bmiHeader.biWidth = NewWidth;
+ bmi.bmiHeader.biHeight = -NewHeight;
+ bmi.bmiHeader.biPlanes = 1;
+ bmi.bmiHeader.biBitCount = 32;
+ bmi.bmiHeader.biCompression = BI_RGB;
+
+ BYTE *pBits;
+ HBITMAP ResizedHBmp = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void **)&pBits, NULL, 0);
+ hr = ResizedHBmp ? S_OK : E_OUTOFMEMORY;
+ if (SUCCEEDED(hr)) {
+ IWICBitmapScaler *pIScaler;
+ hr = pImgFac->CreateBitmapScaler(&pIScaler);
+ hr = pIScaler->Initialize(WICBmp, NewWidth, NewHeight, WICBitmapInterpolationModeFant);
+
+ WICRect rect = {0, 0, NewWidth, NewHeight};
+ hr = pIScaler->CopyPixels(&rect, NewWidth * 4, NewWidth * NewHeight * 4, pBits);
+
+ if (SUCCEEDED(hr)) {
+ DeleteObject(*phbmp);
+ *phbmp = ResizedHBmp;
+ }
+ else {
+ DeleteObject(ResizedHBmp);
+ }
+
+ pIScaler->Release();
+ }
+ WICBmp->Release();
+ pImgFac->Release();
+ }
+ else {
+ hr = S_OK;
+ }
+ return hr;
+}
diff --git a/source/blender/blendthumb/src/BlendThumb.def b/source/blender/blendthumb/src/blendthumb_win32.def
index 71f9236735f..71f9236735f 100644
--- a/source/blender/blendthumb/src/BlendThumb.def
+++ b/source/blender/blendthumb/src/blendthumb_win32.def
diff --git a/source/blender/blendthumb/src/BlendThumb.rc b/source/blender/blendthumb/src/blendthumb_win32.rc
index 5dfd416b0c5..5dfd416b0c5 100644
--- a/source/blender/blendthumb/src/BlendThumb.rc
+++ b/source/blender/blendthumb/src/blendthumb_win32.rc
diff --git a/source/blender/blendthumb/src/Dll.cpp b/source/blender/blendthumb/src/blendthumb_win32_dll.cc
index 7f10777f884..7f10777f884 100644
--- a/source/blender/blendthumb/src/Dll.cpp
+++ b/source/blender/blendthumb/src/blendthumb_win32_dll.cc
diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt
index 47fb2642da1..45bfc3d6bdb 100644
--- a/source/creator/CMakeLists.txt
+++ b/source/creator/CMakeLists.txt
@@ -516,8 +516,7 @@ if(UNIX AND NOT APPLE)
)
install(
- PROGRAMS
- ${CMAKE_SOURCE_DIR}/release/bin/blender-thumbnailer.py
+ TARGETS blender-thumbnailer
DESTINATION "."
)
@@ -560,7 +559,7 @@ if(UNIX AND NOT APPLE)
DESTINATION share/icons/hicolor/symbolic/apps
)
install(
- PROGRAMS ${CMAKE_SOURCE_DIR}/release/bin/blender-thumbnailer.py
+ TARGETS blender-thumbnailer
DESTINATION bin
)
set(BLENDER_TEXT_FILES_DESTINATION share/doc/blender)