Refactor shell thumbnail extension code
1. Drop obsolete #ifndef DONT_HAVE_GDIPLUS, introduced in commit 06c8108fe5c196aec2888d9b236f595f2bcb1c46 (Attempt to make this build with MinGW, 2011-06-24). 2. Improve API methods implementations. 2.1. In IUnknown::Release, make sure to check the value returned from decrement, not the member (that could potentially be modified in the meantime again). 2.2. In ISequentialStream::Read, don't return S_OK for too large read; and make sure to set pcbRead even on zero-byte read. 2.3. In IStream::Seek, check dwOrigin value; handle STREAM_SEEK_END correctly, without subtracting an extra 1; don't fail on too large positive offset (that is explicitly documented as OK); and don't ignore the plibNewPosition argument (callers expect the new offset there). 2.4. In IStream::Stat, check grfStatFlag value; handle OOM; don't limit the returned offset to 32 bits. 3. Make StreamOnZipBuffer own the buffer. It user a reference to temporaries; but the object is refcounted, and when passed to an API, it could potentially outlive the temporaries it depended on. 4. Move CThumbviewer definition to CXX, and simplify the HXX. Change-Id: I5b31a8bf66651862d730cc346948dc04f984fddb Reviewed-on: https://gerrit.libreoffice.org/c/core/+/184807 Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com> Tested-by: Jenkins
This commit is contained in:
parent
923da8a385
commit
f4b72c08f9
@ -17,83 +17,10 @@
|
||||
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_SHELL_INC_INTERNAL_THUMBVIEWER_HXX
|
||||
#define INCLUDED_SHELL_INC_INTERNAL_THUMBVIEWER_HXX
|
||||
#pragma once
|
||||
|
||||
#include <objidl.h>
|
||||
#include <shlobj.h>
|
||||
#ifndef DONT_HAVE_GDIPLUS
|
||||
#include <gdiplus.h>
|
||||
#endif
|
||||
#include <string>
|
||||
|
||||
class CThumbviewer : public IPersistFile, public IExtractImage
|
||||
{
|
||||
public:
|
||||
CThumbviewer(LONG RefCnt = 1);
|
||||
virtual ~CThumbviewer();
|
||||
|
||||
|
||||
// IUnknown methods
|
||||
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
|
||||
REFIID riid,
|
||||
void __RPC_FAR *__RPC_FAR *ppvObject) override;
|
||||
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
|
||||
virtual ULONG STDMETHODCALLTYPE Release() override;
|
||||
|
||||
|
||||
// IExtractImage methods
|
||||
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE Extract(HBITMAP *phBmpImage) override;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetLocation(
|
||||
LPWSTR pszPathBuffer,
|
||||
DWORD cchMax,
|
||||
DWORD *pdwPriority,
|
||||
const SIZE *prgSize,
|
||||
DWORD dwRecClrDepth,
|
||||
DWORD *pdwFlags) override;
|
||||
|
||||
|
||||
// IPersist methods
|
||||
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID* pClassID) override;
|
||||
|
||||
|
||||
// IPersistFile methods
|
||||
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE IsDirty() override;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE Load(
|
||||
/* [in] */ LPCOLESTR pszFileName,
|
||||
/* [in] */ DWORD dwMode) override;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE Save(
|
||||
/* [unique][in] */ LPCOLESTR pszFileName,
|
||||
/* [in] */ BOOL fRemember) override;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SaveCompleted(
|
||||
/* [unique][in] */ LPCOLESTR pszFileName) override;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetCurFile(
|
||||
/* [out] */ LPOLESTR __RPC_FAR *ppszFileName) override;
|
||||
|
||||
private:
|
||||
LONG ref_count_;
|
||||
std::wstring filename_;
|
||||
SIZE thumbnail_size_;
|
||||
DWORD color_depth_;
|
||||
ULONG_PTR gdiplus_token_;
|
||||
Gdiplus::Bitmap* signet_;
|
||||
};
|
||||
|
||||
#endif
|
||||
IExtractImage* CreateThumbviewer();
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
|
@ -102,7 +102,7 @@ HRESULT STDMETHODCALLTYPE CClassFactory::CreateInstance(
|
||||
pUnk = new CColumnInfo();
|
||||
|
||||
else if (CLSID_THUMBVIEWER_HANDLER == m_Clsid)
|
||||
pUnk = static_cast<IExtractImage*>(new CThumbviewer());
|
||||
pUnk = CreateThumbviewer();
|
||||
|
||||
if (nullptr == pUnk)
|
||||
{
|
||||
|
@ -17,9 +17,6 @@
|
||||
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
||||
*/
|
||||
|
||||
#ifndef DONT_HAVE_GDIPLUS
|
||||
|
||||
|
||||
#include <global.hxx>
|
||||
|
||||
#include <thumbviewer.hxx>
|
||||
@ -37,26 +34,32 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <shellapi.h>
|
||||
#include <objidl.h>
|
||||
#include <shlobj.h>
|
||||
#include <gdiplus.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace internal
|
||||
namespace
|
||||
{
|
||||
/* The signet.png used for thumbnails of signed documents
|
||||
is contained as resource in this module, the resource
|
||||
id is 2000 */
|
||||
static void LoadSignetImageFromResource(ZipFile::ZipContentBuffer_t& buffer)
|
||||
static ZipFile::ZipContentBuffer_t LoadSignetImageFromResource()
|
||||
{
|
||||
HRSRC hrc = FindResourceW(g_hModule, L"#2000", reinterpret_cast<LPWSTR>(RT_RCDATA));
|
||||
DWORD size = SizeofResource(g_hModule, hrc);
|
||||
HGLOBAL hglob = LoadResource(g_hModule, hrc);
|
||||
char* data = static_cast<char*>(LockResource(hglob));
|
||||
buffer = ZipFile::ZipContentBuffer_t(data, data + size);
|
||||
return ZipFile::ZipContentBuffer_t(data, data + size);
|
||||
}
|
||||
|
||||
static bool IsSignedDocument(const ZipFile* zipfile)
|
||||
static bool IsSignedDocument(const ZipFile& zipfile)
|
||||
{
|
||||
return zipfile->HasContent("META-INF/documentsignatures.xml");
|
||||
return zipfile.HasContent("META-INF/documentsignatures.xml");
|
||||
}
|
||||
|
||||
static Gdiplus::Point CalcSignetPosition(
|
||||
@ -80,9 +83,6 @@ namespace internal
|
||||
|
||||
return Gdiplus::Point(x,y);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Gdiplus::Rect CalcScaledAspectRatio(const Gdiplus::Rect& src, const Gdiplus::Rect& dest)
|
||||
{
|
||||
@ -98,7 +98,18 @@ Gdiplus::Rect CalcScaledAspectRatio(const Gdiplus::Rect& src, const Gdiplus::Rec
|
||||
class StreamOnZipBuffer final : public IStream
|
||||
{
|
||||
public:
|
||||
explicit StreamOnZipBuffer(const ZipFile::ZipContentBuffer_t& zip_buffer);
|
||||
struct SmartPtr
|
||||
{
|
||||
~SmartPtr() { p->Release(); }
|
||||
operator IStream*() { return p; }
|
||||
|
||||
IStream* p;
|
||||
};
|
||||
|
||||
static SmartPtr Create(ZipFile::ZipContentBuffer_t&& zip_buffer)
|
||||
{
|
||||
return { new StreamOnZipBuffer(std::move(zip_buffer)) };
|
||||
}
|
||||
|
||||
// IUnknown
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
@ -119,32 +130,28 @@ public:
|
||||
virtual HRESULT STDMETHODCALLTYPE Clone(IStream **ppstm) override;
|
||||
|
||||
private:
|
||||
LONG ref_count_;
|
||||
const ZipFile::ZipContentBuffer_t& ref_zip_buffer_;
|
||||
size_t pos_;
|
||||
explicit StreamOnZipBuffer(ZipFile::ZipContentBuffer_t&& zip_buffer)
|
||||
: zip_buffer_(std::move(zip_buffer))
|
||||
{
|
||||
}
|
||||
|
||||
std::atomic<ULONG> ref_count_ = 1;
|
||||
const ZipFile::ZipContentBuffer_t zip_buffer_;
|
||||
size_t pos_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
StreamOnZipBuffer::StreamOnZipBuffer(const ZipFile::ZipContentBuffer_t& zip_buffer) :
|
||||
ref_count_(1),
|
||||
ref_zip_buffer_(zip_buffer),
|
||||
pos_(0)
|
||||
{
|
||||
}
|
||||
|
||||
// IUnknown methods
|
||||
|
||||
ULONG STDMETHODCALLTYPE StreamOnZipBuffer::AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&ref_count_);
|
||||
return ++ref_count_;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE StreamOnZipBuffer::Release()
|
||||
{
|
||||
LONG refcnt = InterlockedDecrement(&ref_count_);
|
||||
const ULONG refcnt = --ref_count_;
|
||||
|
||||
if (0 == ref_count_)
|
||||
if (0 == refcnt)
|
||||
delete this;
|
||||
|
||||
return refcnt;
|
||||
@ -153,13 +160,11 @@ ULONG STDMETHODCALLTYPE StreamOnZipBuffer::Release()
|
||||
HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::QueryInterface(REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject)
|
||||
{
|
||||
*ppvObject = nullptr;
|
||||
IUnknown* pUnk = nullptr;
|
||||
|
||||
if ((IID_IUnknown == riid) || (IID_IStream == riid))
|
||||
{
|
||||
pUnk = this;
|
||||
pUnk->AddRef();
|
||||
*ppvObject = pUnk;
|
||||
*ppvObject = this;
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
return E_NOINTERFACE;
|
||||
@ -170,75 +175,78 @@ HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::Read(void *pv, ULONG cb, ULONG *pcb
|
||||
if (pv == nullptr)
|
||||
return STG_E_INVALIDPOINTER;
|
||||
|
||||
size_t size = ref_zip_buffer_.size();
|
||||
|
||||
if (pos_ > size)
|
||||
return S_FALSE;
|
||||
|
||||
char* p = static_cast<char*>(pv);
|
||||
ULONG read = 0;
|
||||
|
||||
for ( ;(pos_ < size) && (cb > 0); pos_++, cb--, read++)
|
||||
*p++ = ref_zip_buffer_[pos_];
|
||||
const size_t available = pos_ < zip_buffer_.size() ? zip_buffer_.size() - pos_ : 0;
|
||||
const ULONG read = min(available, cb);
|
||||
if (read > 0)
|
||||
{
|
||||
std::copy_n(zip_buffer_.data() + pos_, read, p);
|
||||
pos_ += read;
|
||||
}
|
||||
|
||||
if (pcbRead)
|
||||
*pcbRead = read;
|
||||
|
||||
return S_OK;
|
||||
return read == cb ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *)
|
||||
HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin,
|
||||
ULARGE_INTEGER* plibNewPosition)
|
||||
{
|
||||
__int64 size = static_cast<__int64>(ref_zip_buffer_.size());
|
||||
__int64 p = 0;
|
||||
const __int64 size = zip_buffer_.size();
|
||||
__int64 p;
|
||||
|
||||
switch (dwOrigin)
|
||||
{
|
||||
case STREAM_SEEK_SET:
|
||||
p = 0;
|
||||
break;
|
||||
case STREAM_SEEK_CUR:
|
||||
p = static_cast<__int64>(pos_);
|
||||
p = pos_;
|
||||
break;
|
||||
case STREAM_SEEK_END:
|
||||
p = size - 1;
|
||||
p = size;
|
||||
break;
|
||||
default:
|
||||
return STG_E_INVALIDFUNCTION;
|
||||
}
|
||||
|
||||
HRESULT hr = STG_E_INVALIDFUNCTION;
|
||||
|
||||
p += dlibMove.QuadPart;
|
||||
if (p < 0)
|
||||
return STG_E_INVALIDFUNCTION;
|
||||
|
||||
if ( ( p >= 0 ) && (p < size) )
|
||||
{
|
||||
pos_ = static_cast<size_t>(p);
|
||||
hr = S_OK;
|
||||
}
|
||||
return hr;
|
||||
if (p > size)
|
||||
p = size;
|
||||
|
||||
pos_ = p;
|
||||
if (plibNewPosition)
|
||||
*plibNewPosition = { .QuadPart = pos_ };
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::Stat(STATSTG *pstatstg, DWORD grfStatFlag)
|
||||
{
|
||||
if (pstatstg == nullptr)
|
||||
return STG_E_INVALIDPOINTER;
|
||||
if (grfStatFlag != STATFLAG_DEFAULT && grfStatFlag != STATFLAG_NONAME)
|
||||
return STG_E_INVALIDFLAG;
|
||||
|
||||
ZeroMemory(pstatstg, sizeof(STATSTG));
|
||||
|
||||
if (grfStatFlag == STATFLAG_DEFAULT)
|
||||
if (grfStatFlag != STATFLAG_NONAME)
|
||||
{
|
||||
size_t sz = 4 * sizeof(wchar_t);
|
||||
wchar_t* name = static_cast<wchar_t*>(CoTaskMemAlloc(sz));
|
||||
ZeroMemory(name, sz);
|
||||
memcpy(name, L"png", 3 * sizeof(wchar_t));
|
||||
if (!name)
|
||||
return STG_E_INSUFFICIENTMEMORY;
|
||||
wcscpy(name, L"png");
|
||||
pstatstg->pwcsName = name;
|
||||
}
|
||||
|
||||
pstatstg->type = STGTY_LOCKBYTES;
|
||||
|
||||
ULARGE_INTEGER uli;
|
||||
uli.LowPart = static_cast<DWORD>(ref_zip_buffer_.size());
|
||||
uli.HighPart = 0;
|
||||
|
||||
pstatstg->cbSize = uli;
|
||||
static_assert(std::is_integral_v<decltype(pstatstg->cbSize.QuadPart)>);
|
||||
pstatstg->cbSize = { .QuadPart = zip_buffer_.size() };
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@ -267,23 +275,69 @@ HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::UnlockRegion(ULARGE_INTEGER, ULARGE
|
||||
HRESULT STDMETHODCALLTYPE StreamOnZipBuffer::Clone(IStream **)
|
||||
{ return E_NOTIMPL; }
|
||||
|
||||
class CThumbviewer : public IPersistFile, public IExtractImage
|
||||
{
|
||||
public:
|
||||
CThumbviewer();
|
||||
virtual ~CThumbviewer();
|
||||
|
||||
CThumbviewer::CThumbviewer(LONG RefCnt) :
|
||||
ref_count_(RefCnt)
|
||||
// IUnknown methods
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,
|
||||
void __RPC_FAR* __RPC_FAR* ppvObject) override;
|
||||
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
|
||||
virtual ULONG STDMETHODCALLTYPE Release() override;
|
||||
|
||||
// IExtractImage methods
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE Extract(HBITMAP* phBmpImage) override;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetLocation(LPWSTR pszPathBuffer, DWORD cchMax,
|
||||
DWORD* pdwPriority, const SIZE* prgSize,
|
||||
DWORD dwRecClrDepth, DWORD* pdwFlags) override;
|
||||
|
||||
// IPersist methods
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID* pClassID) override;
|
||||
|
||||
// IPersistFile methods
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE IsDirty() override;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE Load(
|
||||
/* [in] */ LPCOLESTR pszFileName,
|
||||
/* [in] */ DWORD dwMode) override;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE Save(
|
||||
/* [unique][in] */ LPCOLESTR pszFileName,
|
||||
/* [in] */ BOOL fRemember) override;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SaveCompleted(
|
||||
/* [unique][in] */ LPCOLESTR pszFileName) override;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetCurFile(
|
||||
/* [out] */ LPOLESTR __RPC_FAR* ppszFileName) override;
|
||||
|
||||
private:
|
||||
std::atomic<ULONG> ref_count_ = 1;
|
||||
DWORD color_depth_ = 0;
|
||||
std::wstring filename_;
|
||||
SIZE thumbnail_size_ = { .cx = 0, .cy = 0 };
|
||||
ULONG_PTR gdiplus_token_;
|
||||
Gdiplus::Bitmap* signet_;
|
||||
};
|
||||
|
||||
CThumbviewer::CThumbviewer()
|
||||
{
|
||||
InterlockedIncrement(&g_DllRefCnt);
|
||||
|
||||
thumbnail_size_.cx = 0;
|
||||
thumbnail_size_.cy = 0;
|
||||
|
||||
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
|
||||
Gdiplus::GdiplusStartup(&gdiplus_token_, &gdiplusStartupInput, nullptr);
|
||||
|
||||
ZipFile::ZipContentBuffer_t img_data;
|
||||
internal::LoadSignetImageFromResource(img_data);
|
||||
IStream* stream = new StreamOnZipBuffer(img_data);
|
||||
signet_ = new Gdiplus::Bitmap(stream, TRUE);
|
||||
stream->Release();
|
||||
signet_ = new Gdiplus::Bitmap(
|
||||
StreamOnZipBuffer::Create(LoadSignetImageFromResource()), TRUE);
|
||||
}
|
||||
|
||||
CThumbviewer::~CThumbviewer()
|
||||
@ -298,20 +352,17 @@ CThumbviewer::~CThumbviewer()
|
||||
HRESULT STDMETHODCALLTYPE CThumbviewer::QueryInterface(REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject)
|
||||
{
|
||||
*ppvObject = nullptr;
|
||||
IUnknown* pUnk = nullptr;
|
||||
|
||||
if ((IID_IUnknown == riid) || (IID_IPersistFile == riid))
|
||||
{
|
||||
pUnk = static_cast<IPersistFile*>(this);
|
||||
pUnk->AddRef();
|
||||
*ppvObject = pUnk;
|
||||
*ppvObject = static_cast<IPersistFile*>(this);
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
else if (IID_IExtractImage == riid)
|
||||
{
|
||||
pUnk = static_cast<IExtractImage*>(this);
|
||||
pUnk->AddRef();
|
||||
*ppvObject = pUnk;
|
||||
*ppvObject = static_cast<IExtractImage*>(this);
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
return E_NOINTERFACE;
|
||||
@ -319,14 +370,14 @@ HRESULT STDMETHODCALLTYPE CThumbviewer::QueryInterface(REFIID riid, void __RPC_F
|
||||
|
||||
ULONG STDMETHODCALLTYPE CThumbviewer::AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&ref_count_);
|
||||
return ++ref_count_;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE CThumbviewer::Release()
|
||||
{
|
||||
LONG refcnt = InterlockedDecrement(&ref_count_);
|
||||
const ULONG refcnt = --ref_count_;
|
||||
|
||||
if (0 == ref_count_)
|
||||
if (0 == refcnt)
|
||||
delete this;
|
||||
|
||||
return refcnt;
|
||||
@ -343,19 +394,17 @@ HRESULT STDMETHODCALLTYPE CThumbviewer::Extract(HBITMAP *phBmpImage)
|
||||
try
|
||||
{
|
||||
std::wstring fname = getShortPathName( filename_ );
|
||||
std::unique_ptr<ZipFile> zipfile( new ZipFile( fname ) );
|
||||
ZipFile zipfile(fname);
|
||||
|
||||
if (zipfile->HasContent(THUMBNAIL_CONTENT))
|
||||
if (zipfile.HasContent(THUMBNAIL_CONTENT))
|
||||
{
|
||||
ZipFile::ZipContentBuffer_t thumbnail;
|
||||
zipfile->GetUncompressedContent(THUMBNAIL_CONTENT, thumbnail);
|
||||
IStream* stream = new StreamOnZipBuffer(thumbnail);
|
||||
zipfile.GetUncompressedContent(THUMBNAIL_CONTENT, thumbnail);
|
||||
|
||||
Gdiplus::Bitmap thumbnail_png(stream, TRUE);
|
||||
Gdiplus::Bitmap thumbnail_png(StreamOnZipBuffer::Create(std::move(thumbnail)), TRUE);
|
||||
|
||||
if ((thumbnail_png.GetHeight() == 0) || (thumbnail_png.GetWidth() == 0))
|
||||
{
|
||||
stream->Release();
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
@ -414,12 +463,12 @@ HRESULT STDMETHODCALLTYPE CThumbviewer::Extract(HBITMAP *phBmpImage)
|
||||
thumbnail_png.GetWidth(), thumbnail_png.GetHeight(), Gdiplus::UnitPixel);
|
||||
|
||||
/* Add a signet sign to the thumbnail of signed documents */
|
||||
if (internal::IsSignedDocument(zipfile.get()))
|
||||
if (IsSignedDocument(zipfile))
|
||||
{
|
||||
double SCALING_FACTOR = 0.6;
|
||||
Gdiplus::Rect signet_scaled(
|
||||
0, 0, static_cast<INT>(signet_->GetWidth() * SCALING_FACTOR), static_cast<INT>(signet_->GetHeight() * SCALING_FACTOR));
|
||||
Gdiplus::Point pos_signet = internal::CalcSignetPosition(canvas_thumbnail, border_rect, signet_scaled);
|
||||
Gdiplus::Point pos_signet = CalcSignetPosition(canvas_thumbnail, border_rect, signet_scaled);
|
||||
Gdiplus::Rect dest(pos_signet.X, pos_signet.Y, signet_scaled.GetRight(), signet_scaled.GetBottom());
|
||||
|
||||
stat = graphics.DrawImage(
|
||||
@ -439,7 +488,6 @@ HRESULT STDMETHODCALLTYPE CThumbviewer::Extract(HBITMAP *phBmpImage)
|
||||
}
|
||||
|
||||
ReleaseDC(hwnd, hdc);
|
||||
stream->Release();
|
||||
}
|
||||
}
|
||||
catch(std::exception&)
|
||||
@ -494,7 +542,8 @@ HRESULT STDMETHODCALLTYPE CThumbviewer::SaveCompleted(LPCOLESTR)
|
||||
HRESULT STDMETHODCALLTYPE CThumbviewer::GetCurFile(LPOLESTR __RPC_FAR*)
|
||||
{ return E_NOTIMPL; }
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // DONT_HAVE_GDIPLUS
|
||||
IExtractImage* CreateThumbviewer() { return new CThumbviewer; }
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
|
Loading…
x
Reference in New Issue
Block a user