Add Chromium-only Blender WebEngine parity work
This commit is contained in:
105
blender-5.2.0/source/blender/blendthumb/CMakeLists.txt
Normal file
105
blender-5.2.0/source/blender/blendthumb/CMakeLists.txt
Normal file
@@ -0,0 +1,105 @@
|
||||
# SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Shared Thumbnail Extraction Logic
|
||||
|
||||
include_directories(
|
||||
../blenlib
|
||||
../blenloader_core
|
||||
../makesdna
|
||||
../../../intern/guardedalloc
|
||||
)
|
||||
|
||||
include_directories(
|
||||
SYSTEM
|
||||
${ZLIB_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
set(SRC
|
||||
src/blendthumb.hh
|
||||
src/blendthumb_extract.cc
|
||||
src/blendthumb_png.cc
|
||||
)
|
||||
|
||||
if(WITH_BUILDINFO)
|
||||
list(APPEND SRC
|
||||
"$<TARGET_OBJECTS:buildinfoobj>"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Build `BlendThumb.dll`
|
||||
|
||||
set(SRC_WIN32
|
||||
src/blendthumb_win32.cc
|
||||
src/blendthumb_win32.def
|
||||
src/blendthumb_win32.rc
|
||||
src/blendthumb_win32_dll.cc
|
||||
)
|
||||
|
||||
add_library(BlendThumb SHARED ${SRC} ${SRC_WIN32})
|
||||
|
||||
setup_platform_linker_flags(BlendThumb)
|
||||
setup_platform_linker_libs(BlendThumb)
|
||||
|
||||
target_link_libraries(BlendThumb PRIVATE bf_blenlib bf_blenloader_core dbghelp.lib Version.lib Comctl32.lib)
|
||||
# `blenlib` drags in a whole bunch of dependencies on shared libraries, none of which are used
|
||||
# by `blenthumb`, but will cause load issues since the debug linker will not eliminate them.
|
||||
# Link with /OPT:ref to force elimination of those unused dependencies this is already
|
||||
# enabled by default on the release mode flags.
|
||||
set_target_properties(BlendThumb PROPERTIES LINK_FLAGS "/OPT:ref")
|
||||
set_target_properties(BlendThumb PROPERTIES LINK_FLAGS_DEBUG "/NODEFAULTLIB:msvcrt")
|
||||
set_target_properties(BlendThumb PROPERTIES VS_GLOBAL_VcpkgEnabled "false")
|
||||
|
||||
elseif(APPLE)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Build `blender-thumbnailer.appex` app extension.
|
||||
set(SRC_APPEX
|
||||
src/thumbnail_provider.h
|
||||
src/thumbnail_provider.mm
|
||||
)
|
||||
|
||||
add_executable(blender-thumbnailer MACOSX_BUNDLE ${SRC} ${SRC_APPEX})
|
||||
setup_platform_linker_flags(blender-thumbnailer)
|
||||
setup_platform_linker_libs(blender-thumbnailer)
|
||||
target_link_libraries(blender-thumbnailer
|
||||
bf_blenlib
|
||||
bf_blenloader_core
|
||||
# Avoid linker error about undefined _main symbol.
|
||||
"-e _NSExtensionMain"
|
||||
"-framework QuickLookThumbnailing"
|
||||
)
|
||||
# The RPATH here points to the main Blender Resources/lib directory.
|
||||
# Avoid duplicating the large `dylibs` (~300MB).
|
||||
set_target_properties(blender-thumbnailer PROPERTIES
|
||||
INSTALL_RPATH "@loader_path/../../../../Resources/lib"
|
||||
# Prevent Xcode from overwriting the signature.
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ""
|
||||
)
|
||||
# CMake needs the target defined in the same file as add_custom_command.
|
||||
# It needs to be code-signed (ad-hoc in this case)
|
||||
# even on developer machine to generate thumbnails.
|
||||
# Command taken from XCode build process.
|
||||
add_custom_command(
|
||||
TARGET blender-thumbnailer POST_BUILD
|
||||
COMMAND codesign --deep --force --sign -
|
||||
--entitlements "${CMAKE_SOURCE_DIR}/release/darwin/thumbnailer_entitlements.plist"
|
||||
--timestamp=none $<TARGET_BUNDLE_DIR:blender-thumbnailer>
|
||||
)
|
||||
elseif(UNIX)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Build `blender-thumbnailer` executable
|
||||
|
||||
set(SRC_CMD
|
||||
src/blender_thumbnailer.cc
|
||||
)
|
||||
|
||||
add_executable(blender-thumbnailer ${SRC} ${SRC_CMD})
|
||||
setup_platform_linker_flags(blender-thumbnailer)
|
||||
setup_platform_linker_libs(blender-thumbnailer)
|
||||
target_link_libraries(blender-thumbnailer PRIVATE bf_blenlib bf_blenloader_core)
|
||||
target_link_libraries(blender-thumbnailer PRIVATE bf::dependencies::pthreads)
|
||||
endif()
|
||||
@@ -0,0 +1,102 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \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 <iostream>
|
||||
#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"
|
||||
|
||||
using namespace blender;
|
||||
|
||||
/**
|
||||
* 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<Vector<uint8_t>> png_buf_opt = blendthumb_create_png_data_from_thumb(&thumb);
|
||||
if (!png_buf_opt) {
|
||||
err = BT_ERROR;
|
||||
}
|
||||
else {
|
||||
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);
|
||||
}
|
||||
52
blender-5.2.0/source/blender/blendthumb/src/blendthumb.hh
Normal file
52
blender-5.2.0/source/blender/blendthumb/src/blendthumb.hh
Normal file
@@ -0,0 +1,52 @@
|
||||
/* SPDX-FileCopyrightText: 2008-2021 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \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"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct FileReader;
|
||||
|
||||
struct Thumbnail {
|
||||
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<Vector<uint8_t>> blendthumb_create_png_data_from_thumb(const Thumbnail *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);
|
||||
|
||||
/* INTEGER CODES */
|
||||
/* NOTE: this is endianness-sensitive. */
|
||||
#define MAKE_ID(a, b, c, d) (int(d) << 24 | int(c) << 16 | (b) << 8 | (a))
|
||||
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,181 @@
|
||||
/* SPDX-FileCopyrightText: 2008 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup blendthumb
|
||||
*
|
||||
* Expose #blendthumb_create_thumb_from_file that creates the PNG data
|
||||
* but does not write it to a file.
|
||||
*/
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
|
||||
#include "BLI_alloca.h"
|
||||
#include "BLI_endian_defines.h"
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_filereader.h"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BLO_core_bhead.hh"
|
||||
#include "BLO_core_blend_header.hh"
|
||||
#include "BLO_core_file_reader.hh"
|
||||
|
||||
#include "blendthumb.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
BLI_STATIC_ASSERT(ENDIAN_ORDER == L_ENDIAN, "Blender only builds on little endian systems")
|
||||
|
||||
static void thumb_data_vertical_flip(Thumbnail *thumb)
|
||||
{
|
||||
uint32_t *rect = reinterpret_cast<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 = static_cast<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])
|
||||
{
|
||||
int32_t data;
|
||||
memcpy(&data, bytes, 4);
|
||||
/* NOTE: this is endianness-sensitive. */
|
||||
/* PNG is always little-endian, and would require switching on a big-endian system. */
|
||||
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;
|
||||
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 BlenderHeader &header)
|
||||
{
|
||||
BLI_assert(header.endian == L_ENDIAN);
|
||||
/* Iterate over file blocks until we find the thumbnail or run out of data. */
|
||||
while (true) {
|
||||
/* Read next BHead. */
|
||||
const std::optional<BHead> bhead = BLO_readfile_read_bhead(file, header.bhead_type());
|
||||
if (!bhead.has_value()) {
|
||||
/* File has ended. */
|
||||
return BT_INVALID_THUMB;
|
||||
}
|
||||
if (bhead->len < 0) {
|
||||
/* Avoid parsing bad data. */
|
||||
return BT_INVALID_THUMB;
|
||||
}
|
||||
switch (bhead->code) {
|
||||
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]);
|
||||
thumb->height = bytes_to_native_i32(&shape[4]);
|
||||
|
||||
/* Verify that image dimensions and data size make sense. */
|
||||
size_t data_size = bhead->len - sizeof(shape);
|
||||
const uint64_t expected_size = uint64_t(thumb->width) * uint64_t(thumb->height) * 4;
|
||||
if (thumb->width < 0 || thumb->height < 0 || data_size != expected_size) {
|
||||
return BT_INVALID_THUMB;
|
||||
}
|
||||
|
||||
thumb->data = 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, bhead->len)) {
|
||||
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;
|
||||
}
|
||||
|
||||
eThumbStatus blendthumb_create_thumb_from_file(FileReader *rawfile, Thumbnail *thumb)
|
||||
{
|
||||
FileReader *file = BLO_file_reader_uncompressed(rawfile);
|
||||
|
||||
if (file == nullptr) {
|
||||
return BT_ERROR;
|
||||
}
|
||||
|
||||
const BlenderHeaderVariant header_variant = BLO_readfile_blender_header_decode(file);
|
||||
if (!std::holds_alternative<BlenderHeader>(header_variant)) {
|
||||
file->close(file);
|
||||
return BT_ERROR;
|
||||
}
|
||||
const BlenderHeader &header = std::get<BlenderHeader>(header_variant);
|
||||
|
||||
/* Check if the file is new enough to contain a thumbnail. */
|
||||
if (header.file_version < 250) {
|
||||
file->close(file);
|
||||
return BT_EARLY_VERSION;
|
||||
}
|
||||
|
||||
/* Check if the file was written from a big-endian build. */
|
||||
if (header.endian != L_ENDIAN) {
|
||||
file->close(file);
|
||||
return BT_INVALID_FILE;
|
||||
}
|
||||
BLI_assert(header.endian == ENDIAN_ORDER);
|
||||
|
||||
/* Read the thumbnail. */
|
||||
eThumbStatus err = blendthumb_extract_from_file_impl(file, thumb, header);
|
||||
file->close(file);
|
||||
if (err != BT_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
thumb_data_vertical_flip(thumb);
|
||||
return BT_OK;
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
147
blender-5.2.0/source/blender/blendthumb/src/blendthumb_png.cc
Normal file
147
blender-5.2.0/source/blender/blendthumb/src/blendthumb_png.cc
Normal file
@@ -0,0 +1,147 @@
|
||||
/* SPDX-FileCopyrightText: 2008 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \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"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static void png_extend_native_int32(Vector<uint8_t> &output, int32_t data)
|
||||
{
|
||||
/* NOTE: this is endianness-sensitive. */
|
||||
/* PNG is big-endian, its values need to be switched on little-endian systems. */
|
||||
BLI_endian_switch_int32(&data);
|
||||
output.extend_unchecked(Span(reinterpret_cast<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(Vector<uint8_t> &output,
|
||||
const uint32_t tag,
|
||||
const Vector<uint8_t> &data)
|
||||
{
|
||||
uint32_t crc = crc32(0, nullptr, 0);
|
||||
crc = crc32(crc, reinterpret_cast<uint8_t *>(const_cast<uint32_t *>(&tag)), sizeof(tag));
|
||||
crc = crc32(crc, const_cast<uint8_t *>(data.data()), data.size());
|
||||
|
||||
png_extend_native_int32(output, data.size());
|
||||
output.extend_unchecked(
|
||||
Span(reinterpret_cast<uint8_t *>(const_cast<uint32_t *>(&tag)), sizeof(tag)));
|
||||
output.extend_unchecked(data);
|
||||
png_extend_native_int32(output, crc);
|
||||
}
|
||||
|
||||
static 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;
|
||||
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(Span(&thumb->data[i * line_size], line_size));
|
||||
}
|
||||
BLI_assert(final_size == filtered.size());
|
||||
return filtered;
|
||||
}
|
||||
|
||||
static std::optional<Vector<uint8_t>> zlib_compress(const Vector<uint8_t> &data)
|
||||
{
|
||||
ulong uncompressed_size = data.size();
|
||||
uLongf compressed_size = compressBound(uncompressed_size);
|
||||
|
||||
Vector<uint8_t> compressed(compressed_size, 0x00);
|
||||
|
||||
int return_value = compress2(static_cast<uchar *>(compressed.data()),
|
||||
&compressed_size,
|
||||
const_cast<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<Vector<uint8_t>> blendthumb_create_png_data_from_thumb(const Thumbnail *thumb)
|
||||
{
|
||||
if (thumb->data.is_empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/* Create `IDAT` chunk data. */
|
||||
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. */
|
||||
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. */
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
226
blender-5.2.0/source/blender/blendthumb/src/blendthumb_win32.cc
Normal file
226
blender-5.2.0/source/blender/blendthumb/src/blendthumb_win32.cc
Normal file
@@ -0,0 +1,226 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup blendthumb
|
||||
*
|
||||
* Thumbnail from Blend file extraction for MS-Windows.
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#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")
|
||||
|
||||
using namespace blender;
|
||||
|
||||
/**
|
||||
* 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(nullptr) {}
|
||||
|
||||
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 != nullptr) {
|
||||
/* 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.
|
||||
*/
|
||||
struct StreamReader {
|
||||
FileReader reader;
|
||||
|
||||
IStream *_pStream;
|
||||
};
|
||||
|
||||
static int64_t stream_read(FileReader *reader, void *buffer, size_t size)
|
||||
{
|
||||
StreamReader *stream = reinterpret_cast<StreamReader *>(reader);
|
||||
|
||||
ULONG readsize;
|
||||
stream->_pStream->Read(buffer, size, &readsize);
|
||||
stream->reader.offset += readsize;
|
||||
|
||||
return int64_t(readsize);
|
||||
}
|
||||
|
||||
static off64_t stream_seek(FileReader *reader, off64_t offset, int whence)
|
||||
{
|
||||
StreamReader *stream = reinterpret_cast<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 = reinterpret_cast<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 up the thumbnail if required. */
|
||||
if (uint(thumb.width) < cx && uint(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, nullptr, 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(
|
||||
nullptr, &bmi, DIB_RGB_COLORS, (void **)&pBits, nullptr, 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;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
EXPORTS
|
||||
DllGetClassObject PRIVATE
|
||||
DllCanUnloadNow PRIVATE
|
||||
DllRegisterServer PRIVATE
|
||||
DllUnregisterServer PRIVATE
|
||||
@@ -0,0 +1,26 @@
|
||||
#define IDR_VERSION1 1
|
||||
|
||||
IDR_VERSION1 VERSIONINFO
|
||||
FILEVERSION 1,4,0,0
|
||||
PRODUCTVERSION 2,78,0,0
|
||||
FILEOS 0x00000004
|
||||
FILETYPE 0x00000002
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "FFFF04B0"
|
||||
BEGIN
|
||||
VALUE "FileVersion", "1.4\0"
|
||||
VALUE "ProductVersion", "2.78\0"
|
||||
VALUE "FileDescription", "Blender Thumbnail Handler\0"
|
||||
VALUE "OriginalFilename", "BlendThumb.dll\0"
|
||||
VALUE "ProductName", "Blender\0"
|
||||
VALUE "LegalCopyright", "GPL2, 2016\0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x0409, 0x04B0
|
||||
END
|
||||
END
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup blendthumb
|
||||
*
|
||||
* Thumbnail from Blend file extraction for MS-Windows (DLL).
|
||||
*/
|
||||
|
||||
#include <new>
|
||||
#include <objbase.h>
|
||||
#include <shlobj.h> /* For #SHChangeNotify */
|
||||
#include <shlwapi.h>
|
||||
#include <thumbcache.h> /* For IThumbnailProvider */
|
||||
|
||||
extern HRESULT CBlendThumb_CreateInstance(REFIID riid, void **ppv);
|
||||
|
||||
#define SZ_CLSID_BLENDTHUMBHANDLER L"{D45F043D-F17F-4e8a-8435-70971D9FA46D}"
|
||||
#define SZ_BLENDTHUMBHANDLER L"Blender Thumbnail Handler"
|
||||
const CLSID CLSID_BlendThumbHandler = {
|
||||
0xd45f043d, 0xf17f, 0x4e8a, {0x84, 0x35, 0x70, 0x97, 0x1d, 0x9f, 0xa4, 0x6d}};
|
||||
|
||||
typedef HRESULT (*PFNCREATEINSTANCE)(REFIID riid, void **ppvObject);
|
||||
struct CLASS_OBJECT_INIT {
|
||||
const CLSID *pClsid;
|
||||
PFNCREATEINSTANCE pfnCreate;
|
||||
};
|
||||
|
||||
/* Add classes supported by this module here. */
|
||||
const CLASS_OBJECT_INIT c_rgClassObjectInit[] = {
|
||||
{&CLSID_BlendThumbHandler, CBlendThumb_CreateInstance}};
|
||||
|
||||
long g_cRefModule = 0;
|
||||
|
||||
/** Handle the DLL's module */
|
||||
HINSTANCE g_hInst = nullptr;
|
||||
|
||||
/** Standard DLL functions. */
|
||||
STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void *)
|
||||
{
|
||||
if (dwReason == DLL_PROCESS_ATTACH) {
|
||||
g_hInst = hInstance;
|
||||
DisableThreadLibraryCalls(hInstance);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
STDAPI DllCanUnloadNow()
|
||||
{
|
||||
/* Only allow the DLL to be unloaded after all outstanding references have been released. */
|
||||
return (g_cRefModule == 0) ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
void DllAddRef()
|
||||
{
|
||||
InterlockedIncrement(&g_cRefModule);
|
||||
}
|
||||
|
||||
void DllRelease()
|
||||
{
|
||||
InterlockedDecrement(&g_cRefModule);
|
||||
}
|
||||
|
||||
class CClassFactory final : public IClassFactory {
|
||||
public:
|
||||
static HRESULT CreateInstance(REFCLSID clsid,
|
||||
const CLASS_OBJECT_INIT *pClassObjectInits,
|
||||
size_t cClassObjectInits,
|
||||
REFIID riid,
|
||||
void **ppv)
|
||||
{
|
||||
*ppv = nullptr;
|
||||
HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
|
||||
for (size_t i = 0; i < cClassObjectInits; i++) {
|
||||
if (clsid == *pClassObjectInits[i].pClsid) {
|
||||
IClassFactory *pClassFactory = new (std::nothrow)
|
||||
CClassFactory(pClassObjectInits[i].pfnCreate);
|
||||
hr = pClassFactory ? S_OK : E_OUTOFMEMORY;
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = pClassFactory->QueryInterface(riid, ppv);
|
||||
pClassFactory->Release();
|
||||
}
|
||||
/* Match found. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
CClassFactory(PFNCREATEINSTANCE pfnCreate) : _cRef(1), _pfnCreate(pfnCreate)
|
||||
{
|
||||
DllAddRef();
|
||||
}
|
||||
|
||||
/** #IUnknown */
|
||||
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
|
||||
{
|
||||
static const QITAB qit[] = {QITABENT(CClassFactory, IClassFactory), {0}};
|
||||
return QISearch(this, qit, riid, ppv);
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(ULONG) AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&_cRef);
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(ULONG) Release()
|
||||
{
|
||||
long cRef = InterlockedDecrement(&_cRef);
|
||||
if (cRef == 0) {
|
||||
delete this;
|
||||
}
|
||||
return cRef;
|
||||
}
|
||||
|
||||
/** #IClassFactory */
|
||||
IFACEMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
|
||||
{
|
||||
return punkOuter ? CLASS_E_NOAGGREGATION : _pfnCreate(riid, ppv);
|
||||
}
|
||||
|
||||
IFACEMETHODIMP LockServer(BOOL fLock)
|
||||
{
|
||||
if (fLock) {
|
||||
DllAddRef();
|
||||
}
|
||||
else {
|
||||
DllRelease();
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
~CClassFactory()
|
||||
{
|
||||
DllRelease();
|
||||
}
|
||||
|
||||
long _cRef;
|
||||
PFNCREATEINSTANCE _pfnCreate;
|
||||
};
|
||||
|
||||
STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void **ppv)
|
||||
{
|
||||
return CClassFactory::CreateInstance(
|
||||
clsid, c_rgClassObjectInit, ARRAYSIZE(c_rgClassObjectInit), riid, ppv);
|
||||
}
|
||||
|
||||
/**
|
||||
* A struct to hold the information required for a registry entry.
|
||||
*/
|
||||
struct REGISTRY_ENTRY {
|
||||
HKEY hkeyRoot;
|
||||
PCWSTR pszKeyName;
|
||||
PCWSTR pszValueName;
|
||||
DWORD dwValueType;
|
||||
/** These two fields could/should have been a union, but C++ */
|
||||
PCWSTR pszData;
|
||||
/** Only lets you initialize the first field in a union. */
|
||||
DWORD dwData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a registry key (if needed) and sets the default value of the key.
|
||||
*/
|
||||
HRESULT CreateRegKeyAndSetValue(const REGISTRY_ENTRY *pRegistryEntry)
|
||||
{
|
||||
HKEY hKey;
|
||||
HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyExW(pRegistryEntry->hkeyRoot,
|
||||
pRegistryEntry->pszKeyName,
|
||||
0,
|
||||
nullptr,
|
||||
REG_OPTION_NON_VOLATILE,
|
||||
KEY_SET_VALUE,
|
||||
nullptr,
|
||||
&hKey,
|
||||
nullptr));
|
||||
if (SUCCEEDED(hr)) {
|
||||
/* All this just to support #REG_DWORD. */
|
||||
DWORD size;
|
||||
DWORD data;
|
||||
BYTE *lpData = (LPBYTE)pRegistryEntry->pszData;
|
||||
switch (pRegistryEntry->dwValueType) {
|
||||
case REG_SZ:
|
||||
size = ((DWORD)wcslen(pRegistryEntry->pszData) + 1) * sizeof(WCHAR);
|
||||
break;
|
||||
case REG_DWORD:
|
||||
size = sizeof(DWORD);
|
||||
data = pRegistryEntry->dwData;
|
||||
lpData = (BYTE *)&data;
|
||||
break;
|
||||
default:
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
hr = HRESULT_FROM_WIN32(RegSetValueExW(
|
||||
hKey, pRegistryEntry->pszValueName, 0, pRegistryEntry->dwValueType, lpData, size));
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this COM server.
|
||||
*/
|
||||
STDAPI DllRegisterServer()
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
WCHAR szModuleName[MAX_PATH];
|
||||
|
||||
if (!GetModuleFileNameW(g_hInst, szModuleName, ARRAYSIZE(szModuleName))) {
|
||||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
else {
|
||||
const REGISTRY_ENTRY rgRegistryEntries[] = {
|
||||
/* `RootKey KeyName ValueName ValueType Data` */
|
||||
{HKEY_CURRENT_USER,
|
||||
L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER,
|
||||
nullptr,
|
||||
REG_SZ,
|
||||
SZ_BLENDTHUMBHANDLER},
|
||||
{HKEY_CURRENT_USER,
|
||||
L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER L"\\InProcServer32",
|
||||
nullptr,
|
||||
REG_SZ,
|
||||
szModuleName},
|
||||
{HKEY_CURRENT_USER,
|
||||
L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER L"\\InProcServer32",
|
||||
L"ThreadingModel",
|
||||
REG_SZ,
|
||||
L"Apartment"},
|
||||
{HKEY_CURRENT_USER,
|
||||
L"Software\\Classes\\.blend\\",
|
||||
L"Treatment",
|
||||
REG_DWORD,
|
||||
0,
|
||||
0}, /* This doesn't appear to do anything. */
|
||||
{HKEY_CURRENT_USER,
|
||||
L"Software\\Classes\\.blend\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}",
|
||||
nullptr,
|
||||
REG_SZ,
|
||||
SZ_CLSID_BLENDTHUMBHANDLER},
|
||||
};
|
||||
|
||||
hr = S_OK;
|
||||
for (int i = 0; i < ARRAYSIZE(rgRegistryEntries) && SUCCEEDED(hr); i++) {
|
||||
hr = CreateRegKeyAndSetValue(&rgRegistryEntries[i]);
|
||||
}
|
||||
}
|
||||
if (SUCCEEDED(hr)) {
|
||||
/* This tells the shell to invalidate the thumbnail cache.
|
||||
* This is important because any `.blend` files viewed before registering this handler
|
||||
* would otherwise show cached blank thumbnails. */
|
||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this COM server
|
||||
*/
|
||||
STDAPI DllUnregisterServer()
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
const PCWSTR rgpszKeys[] = {
|
||||
L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER,
|
||||
L"Software\\Classes\\.blend\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}"};
|
||||
|
||||
/* Delete the registry entries. */
|
||||
for (int i = 0; i < ARRAYSIZE(rgpszKeys) && SUCCEEDED(hr); i++) {
|
||||
hr = HRESULT_FROM_WIN32(RegDeleteTreeW(HKEY_CURRENT_USER, rgpszKeys[i]));
|
||||
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
|
||||
/* If the registry entry has already been deleted, say S_OK. */
|
||||
hr = S_OK;
|
||||
}
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
#import <QuickLookThumbnailing/QuickLookThumbnailing.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ThumbnailProvider : QLThumbnailProvider
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,186 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#import <AppKit/NSImage.h>
|
||||
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_filereader.h"
|
||||
#include "BLI_utility_mixins.hh"
|
||||
#include "blendthumb.hh"
|
||||
|
||||
#include "thumbnail_provider.h"
|
||||
/**
|
||||
* This section intends to list the important steps for creating a thumbnail extension.
|
||||
* qlgenerator has been deprecated and removed in platforms we support. App extensions are the way
|
||||
* forward. But there's little guidance on how to do it outside Xcode.
|
||||
*
|
||||
* The process of thumbnail generation goes something like this:
|
||||
* 1. If an app is launched, or is registered with lsregister, its plugins also get registered.
|
||||
* 2. When a file thumbnail in Finder or QuickLook is requested, the system looks for a plugin
|
||||
* that supports the file type UTI.
|
||||
* 3. The plugin is launched in a sand-boxed environment and should call the handler with a reply.
|
||||
*
|
||||
* # Plugin Info.plist
|
||||
* The Info.plist file should be properly configured with supported content type.
|
||||
*
|
||||
* # Codesigning
|
||||
* The plugin should be codesigned with entitlements at least for sandbox and read-only/
|
||||
* read-write (for access to the given file). It's needed to even run the plugin locally.
|
||||
* com.apple.security.get-task-allow entitlement is required for debugging.
|
||||
*
|
||||
* # Registering the plugin
|
||||
* The plugin should be registered with lsregister. Either by calling lsregister or by launching
|
||||
* the parent app.
|
||||
* /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister
|
||||
* \ -dump | grep blender-thumbnailer
|
||||
*
|
||||
* # Debugging
|
||||
* Since read-only entitlement is there, creating files to log is not possible. So NSLog and
|
||||
* viewing it in Console.app (after triggering a thumbnail) is the way to go. Interesting processes
|
||||
* are: qlmanage, quicklookd, kernel, blender-thumbnailer, secinitd,
|
||||
* com.apple.quicklook.ThumbnailsAgent
|
||||
*
|
||||
* LLDB/ Xcode etc., debuggers can be used to get extra logs than CLI invocation but breakpoints
|
||||
* still are a pain point. /usr/bin/qlmanage is the target executable. Other args to qlmanage
|
||||
* follow. lldb qlmanage -- -t -x a.blend
|
||||
*
|
||||
* # Troubleshooting
|
||||
* - The appex shouldn't have any quarantine flag.
|
||||
* xattr -rl bin/Blender.app/Contents/Plugins/blender-thumbnailer.appex
|
||||
* - Is it registered with lsregister and there isn't a conflict with another plugin taking
|
||||
* precedence? lsregister -dump | grep blender-thumbnailer.appex
|
||||
* - For RBSLaunchRequest error: is the executable flag set? chmod u+x
|
||||
* bin/Blender.app/Contents/PlugIns/blender-thumbnailer.appex/Contents/MacOS/blender-thumbnailer
|
||||
* - Is it codesigned and sandboxed?
|
||||
* codesign --display --verbose --entitlements - --xml \
|
||||
* bin/Blender.app/Contents/Plugins/blender-thumbnailer.appex codesign --deep --force --sign - \
|
||||
* --entitlements ../blender/release/darwin/thumbnailer_entitlements.plist --timestamp=none \
|
||||
* bin/Blender.app/Contents/Plugins/blender-thumbnailer.appex
|
||||
* - Sometimes blender-thumbnailer running in background can be killed.
|
||||
* - qlmanage -r && killall Finder
|
||||
* - The code cannot attempt to do anything outside sandbox like writing to blend.
|
||||
*
|
||||
* # Triggering a thumbnail
|
||||
* - qlmanage -t -x /path/to/file.blend
|
||||
*
|
||||
* # External resources
|
||||
* https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/Quicklook_Programming_Guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40005020-CH1-SW1
|
||||
*/
|
||||
|
||||
class FileDescriptorRAII : blender::NonCopyable, blender::NonMovable {
|
||||
private:
|
||||
int src_fd = -1;
|
||||
|
||||
public:
|
||||
explicit FileDescriptorRAII(const char *file_path)
|
||||
{
|
||||
src_fd = blender::BLI_open(file_path, O_BINARY | O_RDONLY, 0);
|
||||
}
|
||||
|
||||
~FileDescriptorRAII()
|
||||
{
|
||||
if (good()) {
|
||||
int ok = close(src_fd);
|
||||
if (!ok) {
|
||||
NSLog(@"Blender Thumbnailer Error: Failed to close the blend file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool good()
|
||||
{
|
||||
return src_fd > 0;
|
||||
}
|
||||
|
||||
int get()
|
||||
{
|
||||
return src_fd;
|
||||
}
|
||||
};
|
||||
|
||||
static NSError *create_nserror_from_string(NSString *errorStr)
|
||||
{
|
||||
NSLog(@"Blender Thumbnailer Error: %@", errorStr);
|
||||
return [NSError errorWithDomain:@"org.blenderfoundation.blender.thumbnailer"
|
||||
code:-1
|
||||
userInfo:@{NSLocalizedDescriptionKey : errorStr}];
|
||||
}
|
||||
|
||||
static NSImage *generate_nsimage_for_file(const char *src_blend_path, NSError *error)
|
||||
{
|
||||
/* Open source file `src_blend`. */
|
||||
FileDescriptorRAII src_file_fd = FileDescriptorRAII(src_blend_path);
|
||||
if (!src_file_fd.good()) {
|
||||
error = create_nserror_from_string(@"Failed to open blend");
|
||||
return nil;
|
||||
}
|
||||
|
||||
blender::FileReader *file_content = blender::BLI_filereader_new_file(src_file_fd.get());
|
||||
if (file_content == nullptr) {
|
||||
error = create_nserror_from_string(@"Failed to read from blend");
|
||||
return nil;
|
||||
}
|
||||
|
||||
/* Extract thumbnail from file. */
|
||||
blender::Thumbnail thumb;
|
||||
blender::eThumbStatus err = blendthumb_create_thumb_from_file(file_content, &thumb);
|
||||
if (err != blender::BT_OK) {
|
||||
error = create_nserror_from_string(@"Failed to create thumbnail from file");
|
||||
return nil;
|
||||
}
|
||||
|
||||
std::optional<blender::Vector<uint8_t>> png_buf_opt = blendthumb_create_png_data_from_thumb(
|
||||
&thumb);
|
||||
if (!png_buf_opt) {
|
||||
error = create_nserror_from_string(@"Failed to create png data from thumbnail");
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *ns_data = [NSData dataWithBytes:png_buf_opt->data() length:png_buf_opt->size()];
|
||||
NSImage *ns_image = [[NSImage alloc] initWithData:ns_data];
|
||||
return ns_image;
|
||||
}
|
||||
|
||||
@implementation ThumbnailProvider
|
||||
|
||||
- (void)provideThumbnailForFileRequest:(QLFileThumbnailRequest *)request
|
||||
completionHandler:(void (^)(QLThumbnailReply *_Nullable reply,
|
||||
NSError *_Nullable error))handler
|
||||
{
|
||||
|
||||
NSLog(@"Generating thumbnail for %@", request.fileURL.path);
|
||||
@autoreleasepool {
|
||||
NSError *error = nil;
|
||||
NSImage *image = generate_nsimage_for_file(request.fileURL.path.fileSystemRepresentation,
|
||||
error);
|
||||
if (image == nil || image.size.width <= 0 || image.size.height <= 0) {
|
||||
handler(nil, error);
|
||||
return;
|
||||
}
|
||||
|
||||
const CGFloat width_ratio = request.maximumSize.width / image.size.width;
|
||||
const CGFloat height_ratio = request.maximumSize.height / image.size.height;
|
||||
const CGFloat scale_factor = MIN(width_ratio, height_ratio);
|
||||
|
||||
const NSSize context_size = NSMakeSize(image.size.width * scale_factor,
|
||||
image.size.height * scale_factor);
|
||||
|
||||
const NSRect context_rect = NSMakeRect(0, 0, context_size.width, context_size.height);
|
||||
|
||||
QLThumbnailReply *thumbnailReply = [QLThumbnailReply replyWithContextSize:context_size
|
||||
currentContextDrawingBlock:^BOOL {
|
||||
[image drawInRect:context_rect];
|
||||
/* Release the image that was strongly
|
||||
* captured by this block. */
|
||||
[image release];
|
||||
return YES;
|
||||
}];
|
||||
|
||||
/* Return the thumbnail reply. */
|
||||
handler(thumbnailReply, nil);
|
||||
}
|
||||
NSLog(@"Thumbnail generation successfully completed");
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user