diff options
Diffstat (limited to 'source/blender/blendthumb/src/BlenderThumb.cpp')
-rw-r--r-- | source/blender/blendthumb/src/BlenderThumb.cpp | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/source/blender/blendthumb/src/BlenderThumb.cpp b/source/blender/blendthumb/src/BlenderThumb.cpp new file mode 100644 index 00000000000..553428d5b5d --- /dev/null +++ b/source/blender/blendthumb/src/BlenderThumb.cpp @@ -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; +} |