Windows: Move building of blendthumb into the blender codebase.

Previously this was done in the deps builder due to the fact we needed
both 32 and 64 bit versions of this dll and CMAKE does not support that
in a single build folder. Now that 32 bit support has been dropped, this
can be safely moved into the codebase.

Reviewers: brecht

Differential Revision: https://developer.blender.org/D5633
This commit is contained in:
2019-08-30 08:40:08 -06:00
parent 26c110f2d2
commit 66ec72045f
10 changed files with 17 additions and 101 deletions

View File

@@ -0,0 +1,5 @@
EXPORTS
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE

View File

@@ -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

View File

@@ -0,0 +1,320 @@
/*
* 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 <shlwapi.h>
#include <thumbcache.h> // For IThumbnailProvider.
#include <new>
#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 <math.h>
#include <zlib.h>
#include "Wincodec.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 1
MessageBox(0,L"Attach now",L"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;
}

View File

@@ -0,0 +1,273 @@
/*
* 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 <objbase.h>
#include <shlwapi.h>
#include <thumbcache.h> // For IThumbnailProvider.
#include <shlobj.h> // For SHChangeNotify
#include <new>
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 = NULL;
// 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 : public IClassFactory
{
public:
static HRESULT CreateInstance(REFCLSID clsid, const CLASS_OBJECT_INIT *pClassObjectInits, size_t cClassObjectInits, REFIID riid, void **ppv)
{
*ppv = NULL;
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();
}
break; // match found
}
}
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;
PCWSTR pszData;
};
// 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, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL));
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 = (DWORD)pRegistryEntry->pszData;
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, NULL, REG_SZ, SZ_BLENDTHUMBHANDLER},
{HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_BLENDTHUMBHANDLER L"\\InProcServer32", NULL, 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}, // doesn't appear to do anything...
{HKEY_CURRENT_USER, L"Software\\Classes\\.blend\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}", NULL, 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, NULL, NULL);
}
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;
}