diff options
Diffstat (limited to 'source/blender/blendthumb/src')
-rw-r--r-- | source/blender/blendthumb/src/BlenderThumb.cpp | 321 | ||||
-rw-r--r-- | source/blender/blendthumb/src/blender_thumbnailer.cc | 113 | ||||
-rw-r--r-- | source/blender/blendthumb/src/blendthumb.hh | 65 | ||||
-rw-r--r-- | source/blender/blendthumb/src/blendthumb_extract.cc | 257 | ||||
-rw-r--r-- | source/blender/blendthumb/src/blendthumb_png.cc | 158 | ||||
-rw-r--r-- | source/blender/blendthumb/src/blendthumb_win32.cc | 237 | ||||
-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 |
9 files changed, 830 insertions, 321 deletions
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 |