windows-nt/Source/XPSP1/NT/shell/ext/shimgvw/imgdata.cpp

2593 lines
76 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
#include "precomp.h"
#include <runtask.h>
#include "imagprop.h"
#include "shutil.h"
#pragma hdrstop
#define TF_SUSPENDRESUME 0 // turn on to debug CDecodeStream::Suspend/Resume
#define PF_NOSUSPEND 0 // disable suspend and resume (for debugging purposes)
class CDecodeStream;
////////////////////////////////////////////////////////////////////////////
class CEncoderInfo
{
public:
CEncoderInfo();
virtual ~CEncoderInfo();
protected:
HRESULT _GetDataFormatFromPath(LPCWSTR pszPath, GUID *pguidFmt);
HRESULT _GetEncoderList();
HRESULT _GetEncoderFromFormat(const GUID *pfmt, CLSID *pclsidEncoder);
UINT _cEncoders; // number of encoders discovered
ImageCodecInfo *_pici; // array of image encoder classes
};
class CImageFactory : public IShellImageDataFactory, private CEncoderInfo,
public NonATLObject
{
public:
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// IShellImageDataFactory
STDMETHODIMP CreateIShellImageData(IShellImageData **ppshimg);
STDMETHODIMP CreateImageFromFile(LPCWSTR pszPath, IShellImageData **ppshimg);
STDMETHODIMP CreateImageFromStream(IStream *pStream, IShellImageData **ppshimg);
STDMETHODIMP GetDataFormatFromPath(LPCWSTR pszPath, GUID *pDataFormat);
CImageFactory();
private:
~CImageFactory();
LONG _cRef;
CGraphicsInit _cgi;
};
class CImageData : public IShellImageData, IPersistFile, IPersistStream, IPropertySetStorage, private CEncoderInfo,
public NonATLObject
{
public:
CImageData(BOOL fPropertyOnly = FALSE);
static BOOL CALLBACK QueryAbort(void *pvRef);
// IUnknown
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
// IPersist
STDMETHOD(GetClassID)(CLSID *pclsid)
{ *pclsid = CLSID_ShellImageDataFactory; return S_OK; }
// IPersistFile
STDMETHODIMP IsDirty();
STDMETHODIMP Load(LPCOLESTR pszFileName, DWORD dwMode);
STDMETHODIMP Save(LPCOLESTR pszFileName, BOOL fRemember);
STDMETHODIMP SaveCompleted(LPCOLESTR pszFileName);
STDMETHODIMP GetCurFile(LPOLESTR *ppszFileName);
// IPersistStream
STDMETHOD(Load)(IStream *pstm);
STDMETHOD(Save)(IStream *pstm, BOOL fClearDirty);
STDMETHOD(GetSizeMax)(ULARGE_INTEGER *pcbSize)
{ return E_NOTIMPL; }
// IPropertySetStorage methods
STDMETHODIMP Create(REFFMTID fmtid, const CLSID * pclsid, DWORD grfFlags, DWORD grfMode, IPropertyStorage** ppPropStg);
STDMETHODIMP Open(REFFMTID fmtid, DWORD grfMode, IPropertyStorage** ppPropStg);
STDMETHODIMP Delete(REFFMTID fmtid);
STDMETHODIMP Enum(IEnumSTATPROPSETSTG** ppenum);
// IShellImageData
STDMETHODIMP Decode(DWORD dwFlags, ULONG pcx, ULONG pcy);
STDMETHODIMP Draw(HDC hdc, LPRECT prcDest, LPRECT prcSrc);
STDMETHODIMP NextFrame();
STDMETHODIMP NextPage();
STDMETHODIMP PrevPage();
STDMETHODIMP IsTransparent();
STDMETHODIMP IsVector();
STDMETHODIMP IsAnimated()
{ return _fAnimated ? S_OK : S_FALSE; }
STDMETHODIMP IsMultipage()
{ return (!_fAnimated && _cImages > 1) ? S_OK : S_FALSE; }
STDMETHODIMP IsDecoded();
STDMETHODIMP IsPrintable()
{ return S_OK; } // all images are printable
STDMETHODIMP IsEditable()
{ return _fEditable ? S_OK : S_FALSE; }
STDMETHODIMP GetCurrentPage(ULONG *pnPage)
{ *pnPage = _iCurrent; return S_OK; }
STDMETHODIMP GetPageCount(ULONG *pcPages)
{ HRESULT hr = _EnsureImage(); *pcPages = _cImages; return hr; }
STDMETHODIMP SelectPage(ULONG iPage);
STDMETHODIMP GetResolution(ULONG *puResolutionX, ULONG *puResolutionY);
STDMETHODIMP GetRawDataFormat(GUID *pfmt);
STDMETHODIMP GetPixelFormat(PixelFormat *pfmt);
STDMETHODIMP GetSize(SIZE *pSize);
STDMETHODIMP GetDelay(DWORD *pdwDelay);
STDMETHODIMP DisplayName(LPWSTR wszName, UINT cch);
STDMETHODIMP GetProperties(DWORD dwMode, IPropertySetStorage **ppPropSet);
STDMETHODIMP Rotate(DWORD dwAngle);
STDMETHODIMP Scale(ULONG cx, ULONG cy, InterpolationMode hints);
STDMETHODIMP DiscardEdit();
STDMETHODIMP SetEncoderParams(IPropertyBag *ppbEnc);
STDMETHODIMP GetEncoderParams(GUID *pguidFmt, EncoderParameters **ppencParams);
STDMETHODIMP RegisterAbort(IShellImageDataAbort *pAbort, IShellImageDataAbort **ppAbortPrev);
STDMETHODIMP CloneFrame(Image **ppimg);
STDMETHODIMP ReplaceFrame(Image *pimg);
private:
CGraphicsInit _cgi;
LONG _cRef;
DWORD _dwMode; // open mode from IPersistFile::Load()
CDecodeStream *_pstrm; // stream that will produce our data
BOOL _fLoaded; // true once PersistFile or PersistStream have been called
BOOL _fDecoded; // true once Decode ahs been called
DWORD _dwFlags; // flags and size passed to Decode method
int _cxDesired;
int _cyDesired;
Image *_pImage; // source of the images (created from the filename)
// REVIEW: do we need to make these be per-frame/page?
// YES!
Image *_pimgEdited; // edited image
HDPA _hdpaProps; // properties for each frame
DWORD _dwRotation;
BOOL _fDestructive; // not a lossless edit operation
BOOL _fAnimated; // this is an animated stream (eg. not multi page picture)
BOOL _fLoopForever; // loop the animated gif forever
int _cLoop; // loop count (0 forever, n = repeat count)
BOOL _fEditable; // can be edited
GUID _guidFmt; // format GUID (original stream is this)
DWORD _cImages; // number of frames/pages in the image
DWORD _iCurrent; // current frame/page we want to display
PropertyItem *_piAnimDelay; // array of the delay assocated with each frame
BOOL _fPropertyOnly;
BOOL _fPropertyChanged;
// image encoder information (created on demand)
IPropertyBag *_ppbEncoderParams; // property bag with encoder parameters
IShellImageDataAbort *_pAbort; // optional abort callback
CDSA<SHCOLUMNID> _dsaChangedProps; // which properties have changed
private:
~CImageData();
HRESULT _EnsureImage();
HRESULT _SuspendStream();
HRESULT _SetDecodeStream(CDecodeStream *pds);
HRESULT _CreateMemPropSetStorage(IPropertySetStorage **ppss);
HRESULT _PropImgToVariant(PropertyItem *pi, VARIANT *pvar);
HRESULT _GetProperty(PROPID id, VARIANT *pvar, VARTYPE vt);
HRESULT _GetDisplayedImage();
void _SetEditImage(Image *pimgEdit);
HRESULT _SaveImages(IStream *pstrm, GUID * pguidFmt);
HRESULT _ReplaceFile(LPCTSTR pszNewFile);
HRESULT _MakeTempFile(LPWSTR pszFile);
void _AddEncParameter(EncoderParameters *pep, GUID guidProperty, ULONG type, void *pv);
HRESULT _EnsureProperties(IPropertySetStorage **ppss);
HRESULT _CreatePropStorage(IPropertyStorage **ppps, REFFMTID fmtid);
static int _FreeProps(void *pProp, void *pData);
//
// since CImagePropSet objects come and go, we need to persist which properties need updating in the CImageData
//
void _SaveFrameProperties(Image *pimg, LONG iFrame);
static void _PropertyChanged(IShellImageData *pThis, SHCOLUMNID *pscid );
};
////////////////////////////////////////////////////////////////////////////
//
// CDecodeStream
//
// Wraps a regular IStream, but is cancellable and can be
// suspended/resumed to prevent the underlying file from being held
// open unnecessarily.
//
////////////////////////////////////////////////////////////////////////////
class CDecodeStream : public IStream, public NonATLObject
{
public:
CDecodeStream(CImageData *pid, IStream *pstrm);
CDecodeStream(CImageData *pid, LPCTSTR pszFilename, DWORD dwMode);
~CDecodeStream()
{
ASSERT(!_pidOwner);
#ifdef DEBUG // Need #ifdef because we call a function
if (IsFileStream())
{
TraceMsg(TF_SUSPENDRESUME, "ds.Release %s", PathFindFileName(_szFilename));
}
#endif
ATOMICRELEASE(_pstrmInner);
}
HRESULT Suspend();
HRESULT Resume(BOOL fFullLoad = FALSE);
void Reload();
//
// Before releasing, you must Detach to break the backreference.
// Otherwise, the next time somebody calls QueryCancel, we will fault.
//
void Detach()
{
_pidOwner = NULL;
}
BOOL IsFileStream() { return _szFilename[0]; }
LPCTSTR GetFilename() { return _szFilename; }
HRESULT DisplayName(LPWSTR wszName, UINT cch);
// *** IUnknown ***
STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// *** IStream ***
STDMETHODIMP Read(void *pv, ULONG cb, ULONG *pcbRead);
STDMETHODIMP Write(void const *pv, ULONG cb, ULONG *pcbWritten);
STDMETHODIMP Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition);
STDMETHODIMP SetSize(ULARGE_INTEGER libNewSize);
STDMETHODIMP CopyTo(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten);
STDMETHODIMP Commit(DWORD grfCommitFlags);
STDMETHODIMP Revert();
STDMETHODIMP LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType);
STDMETHODIMP UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType);
STDMETHODIMP Stat(STATSTG *pstatstg, DWORD grfStatFlag);
STDMETHODIMP Clone(IStream **ppstm);
private:
void CommonConstruct(CImageData *pid) { _cRef = 1; _pidOwner = pid; _fSuspendable = !(g_dwPrototype & PF_NOSUSPEND);}
HRESULT FilterAccess();
private:
IStream * _pstrmInner;
CImageData *_pidOwner; // NOT REFCOUNTED
LONG _cRef;
LARGE_INTEGER _liPos; // Where we were in the file when we suspended
TCHAR _szFilename[MAX_PATH]; // file we are a stream for
BOOL _fSuspendable;
};
CDecodeStream::CDecodeStream(CImageData *pid, IStream *pstrm)
{
CommonConstruct(pid);
IUnknown_Set((IUnknown**)&_pstrmInner, pstrm);
}
CDecodeStream::CDecodeStream(CImageData *pid, LPCTSTR pszFilename, DWORD dwMode)
{
CommonConstruct(pid);
lstrcpyn(_szFilename, pszFilename, ARRAYSIZE(_szFilename));
// ignore the mode
}
//reload is only used for file streams
void CDecodeStream::Reload()
{
if (IsFileStream())
{
ATOMICRELEASE(_pstrmInner);
if (_fSuspendable)
{
ZeroMemory(&_liPos, sizeof(_liPos));
}
}
}
HRESULT CDecodeStream::Suspend()
{
HRESULT hr;
if (IsFileStream() && _pstrmInner && _fSuspendable)
{
// Remember the file position so we can restore it when we resume
const LARGE_INTEGER liZero = { 0, 0 };
hr = _pstrmInner->Seek(liZero, FILE_CURRENT, (ULARGE_INTEGER*)&_liPos);
if (SUCCEEDED(hr))
{
#ifdef DEBUG // Need #ifdef because we call a function
TraceMsg(TF_SUSPENDRESUME, "ds.Suspend %s, pos=0x%08x",
PathFindFileName(_szFilename), _liPos.LowPart);
#endif
ATOMICRELEASE(_pstrmInner);
hr = S_OK;
}
}
else
{
hr = S_FALSE; // Not suspendable or already suspended
}
return hr;
}
HRESULT CDecodeStream::Resume(BOOL fLoadFull)
{
HRESULT hr;
if (_pstrmInner)
{
return S_OK;
}
if (fLoadFull)
{
_fSuspendable = FALSE;
}
if (IsFileStream())
{
if (PathIsURL(_szFilename))
{
// TODO: use URLMon to load the image, make sure we check for being allowed to go on-line
hr = E_NOTIMPL;
}
else
{
if (!fLoadFull)
{
hr = SHCreateStreamOnFileEx(_szFilename, STGM_READ | STGM_SHARE_DENY_NONE, 0, FALSE, NULL, &_pstrmInner);
if (SUCCEEDED(hr))
{
hr = _pstrmInner->Seek(_liPos, FILE_BEGIN, NULL);
if (SUCCEEDED(hr))
{
#ifdef DEBUG // Need #ifdef because we call a function
TraceMsg(TF_SUSPENDRESUME, "ds.Resumed %s, pos=0x%08x",
PathFindFileName(_szFilename), _liPos.LowPart);
#endif
}
else
{
ATOMICRELEASE(_pstrmInner);
}
}
}
else
{
hr = S_OK;
HANDLE hFile = CreateFile(_szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
LARGE_INTEGER liSize = {0};
// we can't handle huge files
if (GetFileSizeEx(hFile, &liSize) && !liSize.HighPart)
{
DWORD dwToRead = liSize.LowPart;
HGLOBAL hGlobal = GlobalAlloc(GHND, dwToRead);
if (hGlobal)
{
void *pv = GlobalLock(hGlobal);
DWORD dwRead;
if (pv)
{
if (ReadFile(hFile, pv, dwToRead, &dwRead, NULL))
{
ASSERT(dwRead == dwToRead);
GlobalUnlock(hGlobal);
hr = CreateStreamOnHGlobal(hGlobal, TRUE, &_pstrmInner);
}
else
{
GlobalUnlock(hGlobal);
}
}
if (!_pstrmInner)
{
GlobalFree(hGlobal);
}
}
}
CloseHandle(hFile);
}
}
if (SUCCEEDED(hr) && !_pstrmInner)
{
DWORD dw = GetLastError();
hr = HRESULT_FROM_WIN32(dw);
}
}
if (FAILED(hr))
{
#ifdef DEBUG // Need #ifdef because we call a function
TraceMsg(TF_SUSPENDRESUME, "ds.Resume %s failed: %08x",
PathFindFileName(_szFilename), hr);
#endif
}
}
else
{
hr = E_FAIL; // Can't resume without a filename
}
return hr;
}
//
// This function is called at the top of each IStream method to make
// sure that the stream has not been cancelled and resumes it if
// necessary.
//
HRESULT CDecodeStream::FilterAccess()
{
if (_pidOwner && _pidOwner->QueryAbort(_pidOwner))
{
return E_ABORT;
}
return Resume();
}
HRESULT CDecodeStream::DisplayName(LPWSTR wszName, UINT cch)
{
HRESULT hr = E_FAIL;
if (IsFileStream())
{
// from the filename generate the leaf name which we can
// return the name to caller.
LPTSTR pszFilename = PathFindFileName(_szFilename);
if (pszFilename)
{
SHTCharToUnicode(pszFilename, wszName, cch);
hr = S_OK;
}
}
else if (_pstrmInner)
{
// this is a stream, so lets get the display name from the that stream
// and return that into the buffer that the caller has given us.
STATSTG stat;
hr = _pstrmInner->Stat(&stat, 0x0);
if (SUCCEEDED(hr))
{
if (stat.pwcsName)
{
StrCpyN(wszName, stat.pwcsName, cch);
CoTaskMemFree(stat.pwcsName);
}
else
{
hr = E_FAIL;
}
}
}
else
{
hr = E_FAIL;
}
return hr;
}
//
// Now the boring part...
//
// *** IUnknown ***
HRESULT CDecodeStream::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
static const QITAB qit[] =
{
QITABENT(CDecodeStream, IStream),
{ 0 },
};
return QISearch(this, qit, riid, ppvObj);
}
ULONG CDecodeStream::AddRef()
{
return InterlockedIncrement(&_cRef);
}
ULONG CDecodeStream::Release()
{
if (InterlockedDecrement(&_cRef))
{
return _cRef;
}
delete this;
return 0;
}
// *** IStream ***
#define WRAP_METHOD(fn, args, argl) \
HRESULT CDecodeStream::fn args \
{ \
HRESULT hr = FilterAccess(); \
if (SUCCEEDED(hr)) \
{ \
hr = _pstrmInner->fn argl; \
} \
return hr; \
}
WRAP_METHOD(Read, (void *pv, ULONG cb, ULONG *pcbRead), (pv, cb, pcbRead))
WRAP_METHOD(Write, (void const *pv, ULONG cb, ULONG *pcbWritten), (pv, cb, pcbWritten))
WRAP_METHOD(Seek, (LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition),
(dlibMove, dwOrigin, plibNewPosition))
WRAP_METHOD(SetSize, (ULARGE_INTEGER libNewSize), (libNewSize))
WRAP_METHOD(CopyTo, (IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten),
(pstm, cb, pcbRead, pcbWritten))
WRAP_METHOD(Commit, (DWORD grfCommitFlags), (grfCommitFlags))
WRAP_METHOD(Revert, (), ())
WRAP_METHOD(LockRegion, (ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType), (libOffset, cb, dwLockType))
WRAP_METHOD(UnlockRegion, (ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType), (libOffset, cb, dwLockType))
WRAP_METHOD(Stat, (STATSTG *pstatstg, DWORD grfStatFlag), (pstatstg, grfStatFlag))
WRAP_METHOD(Clone, (IStream **ppstm), (ppstm))
#undef WRAP_METHOD
////////////////////////////////////////////////////////////////////////////
class CFmtEnum : public IEnumSTATPROPSETSTG, public NonATLObject
{
public:
STDMETHODIMP Next(ULONG celt, STATPROPSETSTG *rgelt, ULONG *pceltFetched);
STDMETHODIMP Skip(ULONG celt);
STDMETHODIMP Reset(void);
STDMETHODIMP Clone(IEnumSTATPROPSETSTG **ppenum);
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
CFmtEnum(IEnumSTATPROPSETSTG *pEnum);
private:
~CFmtEnum();
IEnumSTATPROPSETSTG *_pEnum;
ULONG _idx;
LONG _cRef;
};
#define HR_FROM_STATUS(x) ((x) == Ok) ? S_OK : E_FAIL
// IUnknown
STDMETHODIMP CImageData::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CImageData, IShellImageData),
QITABENT(CImageData, IPersistFile),
QITABENT(CImageData, IPersistStream),
QITABENT(CImageData, IPropertySetStorage),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG) CImageData::AddRef()
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_(ULONG) CImageData::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
CImageData::CImageData(BOOL fPropertyOnly) : _cRef(1), _cImages(1), _fPropertyOnly(fPropertyOnly), _fPropertyChanged(FALSE)
{
// Catch unexpected STACK allocations which would break us.
ASSERT(_dwMode == 0);
ASSERT(_pstrm == NULL);
ASSERT(_fLoaded == FALSE);
ASSERT(_fDecoded == FALSE);
ASSERT(_dwFlags == 0);
ASSERT(_cxDesired == 0);
ASSERT(_cyDesired == 0);
ASSERT(_pImage == NULL);
ASSERT(_pimgEdited == NULL);
ASSERT(_hdpaProps == NULL);
ASSERT(_dwRotation == 0);
ASSERT(_fDestructive == FALSE);
ASSERT(_fAnimated == FALSE);
ASSERT(_fLoopForever == FALSE);
ASSERT(_cLoop == 0);
ASSERT(_fEditable == FALSE);
ASSERT(_iCurrent == 0);
ASSERT(_piAnimDelay == NULL);
ASSERT(_ppbEncoderParams == NULL);
ASSERT(_pAbort == NULL);
}
CImageData::~CImageData()
{
if (_fPropertyOnly && _fPropertyChanged)
{
Save((LPCTSTR)NULL, FALSE);
}
if (_pstrm)
{
_pstrm->Detach();
_pstrm->Release();
}
if (_pImage)
{
delete _pImage; // discard the pImage object we have been using
_pImage = NULL;
}
if (_pimgEdited)
{
delete _pimgEdited;
_pimgEdited = NULL;
}
if (_piAnimDelay)
LocalFree(_piAnimDelay); // do we have an array of image frame delays to destroy
if (_hdpaProps)
DPA_DestroyCallback(_hdpaProps, _FreeProps, NULL);
if (_fLoaded)
{
_dsaChangedProps.Destroy();
}
ATOMICRELEASE(_pAbort);
}
// IPersistStream
HRESULT CImageData::_SetDecodeStream(CDecodeStream *pds)
{
ASSERT(_pstrm == NULL);
_pstrm = pds;
if (_pstrm)
{
_fLoaded = TRUE;
_dsaChangedProps.Create(10);
return S_OK;
}
else
{
return E_OUTOFMEMORY;
}
}
HRESULT CImageData::Load(IStream *pstrm)
{
if (_fLoaded)
return STG_E_INUSE;
return _SetDecodeStream(new CDecodeStream(this, pstrm));
}
HRESULT CImageData::Save(IStream *pstrm, BOOL fClearDirty)
{
HRESULT hr = _EnsureImage();
if (SUCCEEDED(hr))
{
hr = _SaveImages(pstrm, &_guidFmt);
}
return hr;
}
// IPersistFile methods
HRESULT CImageData::IsDirty()
{
return (_dwRotation || _pimgEdited) ? S_OK : S_FALSE;
}
HRESULT CImageData::Load(LPCOLESTR pszFileName, DWORD dwMode)
{
if (_fLoaded)
return STG_E_INUSE;
if (!*pszFileName)
return E_INVALIDARG;
return _SetDecodeStream(new CDecodeStream(this, pszFileName, dwMode));
}
#define ATTRIBUTES_TEMPFILE (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_TEMPORARY)
// IPersistFile::Save() see SDK docs
HRESULT CImageData::Save(LPCOLESTR pszFile, BOOL fRemember)
{
HRESULT hr = _EnsureImage();
if (SUCCEEDED(hr))
{
// If this fires, then somebody managed to get _EnsureImage to
// succeed without ever actually loading anything... (?)
ASSERT(_pstrm);
if (pszFile == NULL && !_pstrm->IsFileStream())
{
// Trying to "save with same name you loaded from"
// when we weren't loaded from a file to begin with
hr = HRESULT_FROM_WIN32(ERROR_INVALID_NAME);
}
else
{
// we default to saving in the original format that we were given
// if the name is NULL (we will also attempt to replace the original file).
TCHAR szTempFile[MAX_PATH];
GUID guidFmt = _guidFmt; // default to original format
BOOL fReplaceOriginal = _pstrm->IsFileStream() &&
((NULL == pszFile) || (S_OK == IsSameFile(pszFile, _pstrm->GetFilename())));
if (fReplaceOriginal)
{
// we are being told to save to the current file, but we have the current file locked open.
// To get around this we save to a temporary file, close our handles on the current file,
// and then replace the current file with the new file
hr = _MakeTempFile(szTempFile);
pszFile = szTempFile;
}
else if (!_ppbEncoderParams)
{
// the caller did not tell us which encoder to use?
// determine the encoder based on the target file name
hr = _GetDataFormatFromPath(pszFile, &guidFmt);
}
if (SUCCEEDED(hr))
{
// the attributes are important as they need to match those of the
// temp file we created else this call fails
IStream *pstrm;
hr = SHCreateStreamOnFileEx(pszFile, STGM_WRITE | STGM_CREATE,
fReplaceOriginal ? ATTRIBUTES_TEMPFILE : 0, TRUE, NULL, &pstrm);
if (SUCCEEDED(hr))
{
hr = _SaveImages(pstrm, &guidFmt);
pstrm->Release();
if (SUCCEEDED(hr) && fReplaceOriginal)
{
hr = _ReplaceFile(szTempFile);
if (SUCCEEDED(hr))
{
delete _pImage;
_pImage = NULL;
_fDecoded = FALSE;
_pstrm->Reload();
DWORD iCurrentPage = _iCurrent;
hr = Decode(_dwFlags, _cxDesired, _cyDesired);
if (iCurrentPage < _cImages)
_iCurrent = iCurrentPage;
}
}
}
if (FAILED(hr) && fReplaceOriginal)
{
// make sure temp file is gone
DeleteFile(szTempFile);
}
}
}
}
return hr;
}
HRESULT CImageData::SaveCompleted(LPCOLESTR pszFileName)
{
return E_NOTIMPL;
}
HRESULT CImageData::GetCurFile(LPOLESTR *ppszFileName)
{
if (_pstrm && _pstrm->IsFileStream())
return SHStrDup(_pstrm->GetFilename(), ppszFileName);
return E_FAIL;
}
// handle decoding the image this includes updating our cache of the images
HRESULT CImageData::_EnsureImage()
{
if (_fDecoded && _pImage)
return S_OK;
return E_FAIL;
}
HRESULT CImageData::_SuspendStream()
{
HRESULT hr = S_OK;
if (_pstrm)
{
hr = _pstrm->Suspend();
}
return hr;
}
HRESULT CImageData::_GetDisplayedImage()
{
HRESULT hr = _EnsureImage();
if (SUCCEEDED(hr))
{
const CLSID * pclsidFrameDim = _fAnimated ? &FrameDimensionTime : &FrameDimensionPage;
hr = HR_FROM_STATUS(_pImage->SelectActiveFrame(pclsidFrameDim, _iCurrent));
}
return hr;
}
// IShellImageData method
HRESULT CImageData::Decode(DWORD dwFlags, ULONG cx, ULONG cy)
{
if (!_fLoaded)
return E_FAIL;
if (_fDecoded)
return S_FALSE;
HRESULT hr = S_OK;
_dwFlags = dwFlags;
_cxDesired = cx;
_cyDesired = cy;
//
// Resume the stream now so GDI+ won't go nuts trying to detect the
// image type of a file that it can't read...
//
hr = _pstrm->Resume(dwFlags & SHIMGDEC_LOADFULL);
// if that succeeded then we can create an image using the stream and decode
// the images from that. once we are done we will release the objects.
if (SUCCEEDED(hr))
{
_pImage = new Image(_pstrm, TRUE);
if (_pImage)
{
if (Ok != _pImage->GetLastStatus())
{
delete _pImage;
_pImage = NULL;
hr = E_FAIL;
}
else
{
_fEditable = TRUE;
if (_dwFlags & SHIMGDEC_THUMBNAIL)
{
// for thumbnails, _cxDesired and _cyDesired define a bounding rectangle but we
// should maintain the original aspect ratio.
int cxT = _pImage->GetWidth();
int cyT = _pImage->GetHeight();
if (cxT > _cxDesired || cyT > _cyDesired)
{
if (Int32x32To64(_cxDesired, cyT) > Int32x32To64(cxT, _cyDesired))
{
// constrained by height
cxT = MulDiv(cxT, _cyDesired, cyT);
if (cxT < 1) cxT = 1;
cyT = _cyDesired;
}
else
{
// constrained by width
cyT = MulDiv(cyT, _cxDesired, cxT);
if (cyT < 1) cyT = 1;
cxT = _cxDesired;
}
}
Image * pThumbnail;
pThumbnail = _pImage->GetThumbnailImage(cxT, cyT, QueryAbort, this);
//
// GDI+ sometimes forgets to tell us it gave up due to an abort.
//
if (pThumbnail && !QueryAbort(this))
{
delete _pImage;
_pImage = pThumbnail;
}
else
{
delete pThumbnail; // "delete" ignores NULL pointers
hr = E_FAIL;
}
}
else
{
_pImage->GetRawFormat(&_guidFmt); // read the raw format of the file
if (_guidFmt == ImageFormatTIFF)
{
VARIANT var;
if (SUCCEEDED(_GetProperty(PropertyTagExifIFD, &var, VT_UI4)))
{
// TIFF images with an EXIF IFD aren't editable by GDI+
_fEditable = FALSE;
VariantClear(&var);
}
}
// is this an animated/multi page image?
_cImages = _pImage->GetFrameCount(&FrameDimensionPage);
if (_cImages <= 1)
{
_cImages = _pImage->GetFrameCount(&FrameDimensionTime);
if (_cImages > 1)
{
_fAnimated = TRUE;
// store the frame delays in PropertyItem *_piAnimDelay;
UINT cb = _pImage->GetPropertyItemSize(PropertyTagFrameDelay);
if (cb)
{
_piAnimDelay = (PropertyItem*)LocalAlloc(LPTR, cb);
if (_piAnimDelay)
{
if (Ok != _pImage->GetPropertyItem(PropertyTagFrameDelay, cb, _piAnimDelay))
{
LocalFree(_piAnimDelay);
_piAnimDelay = NULL;
}
}
}
}
}
_pImage->GetLastStatus(); // 145081: clear the error from the first call to _pImage->GetFrameCount so that
// the later call to _GetProperty won't immediately fail.
// we wouldn't have to do this always if the gdi interface didn't maintain its own
// error code and fail automatically based on it without allowing us to check it
// without resetting it.
// some decoders will return zero as the frame count when they don't support that dimension
if (0 == _cImages)
_cImages = 1;
// is it a looping image? this will only be if its animated
if (_fAnimated)
{
VARIANT var;
if (SUCCEEDED(_GetProperty(PropertyTagLoopCount, &var, VT_UI4)))
{
_cLoop = var.ulVal;
_fLoopForever = (_cLoop == 0);
VariantClear(&var);
}
}
PixelFormat pf = _pImage->GetPixelFormat();
// can we edit this image? NOTE: The caller needs to ensure that the file is writeable
// all of that jazz, we only check if we have an encoder for this format. Just cause we
// can edit a file doesn't mean the file can be written to the original source location.
// We can't edit images with > 8 bits per channel either
if (_fEditable)
{
_fEditable = !_fAnimated && SUCCEEDED(_GetEncoderFromFormat(&_guidFmt, NULL)) && !IsExtendedPixelFormat(pf);
}
}
}
}
else
{
hr = E_OUTOFMEMORY; // failed to allocate the image decoder
}
}
// Suspend the stream so we don't leave the file open
_SuspendStream();
_fDecoded = TRUE;
return hr;
}
HRESULT CImageData::Draw(HDC hdc, LPRECT prcDest, LPRECT prcSrc)
{
if (!prcDest)
return E_INVALIDARG; // not much chance without a destination to paint into
HRESULT hr = _EnsureImage();
if (SUCCEEDED(hr))
{
Image *pimg = _pimgEdited ? _pimgEdited : _pImage;
RECT rcSrc;
if (prcSrc)
{
rcSrc.left = prcSrc->left;
rcSrc.top = prcSrc->top;
rcSrc.right = RECTWIDTH(*prcSrc);
rcSrc.bottom= RECTHEIGHT(*prcSrc);
}
else
{
rcSrc.left = 0;
rcSrc.top = 0;
rcSrc.right = pimg->GetWidth();
rcSrc.bottom= pimg->GetHeight();
}
Unit unit;
RectF rectf;
if (Ok==pimg->GetBounds(&rectf, &unit) && UnitPixel==unit)
{
rcSrc.left += (int)rectf.X;
rcSrc.top += (int)rectf.Y;
}
// we have a source rectangle so lets apply that when we render this image.
Rect rc(prcDest->left, prcDest->top, RECTWIDTH(*prcDest), RECTHEIGHT(*prcDest));
DWORD dwLayout = SetLayout(hdc, LAYOUT_BITMAPORIENTATIONPRESERVED);
Graphics g(hdc);
g.SetPageUnit(UnitPixel); // WARNING: If you remove this line (as has happened twice since Beta 1) you will break printing.
if (_guidFmt == ImageFormatTIFF)
{
g.SetInterpolationMode(InterpolationModeHighQualityBilinear);
}
hr = HR_FROM_STATUS(g.DrawImage(pimg,
rc,
rcSrc.left, rcSrc.top,
rcSrc.right, rcSrc.bottom,
UnitPixel, NULL, QueryAbort, this));
//
// GDI+ sometimes forgets to tell us it gave up due to an abort.
//
if (SUCCEEDED(hr) && QueryAbort(this))
hr = E_ABORT;
if (GDI_ERROR != dwLayout)
SetLayout(hdc, dwLayout);
}
// Suspend the stream so we don't leave the file open
_SuspendStream();
return hr;
}
HRESULT CImageData::SelectPage(ULONG iPage)
{
if (iPage >= _cImages)
return OLE_E_ENUM_NOMORE;
if (_iCurrent != iPage)
{
// Since we are moving to a different page throw away any edits
DiscardEdit();
}
_iCurrent = iPage;
return _GetDisplayedImage();
}
HRESULT CImageData::NextFrame()
{
if (!_fAnimated)
return S_FALSE; // not animated, so no next frame
// if this is the last image, then lets look at the loop
// counter and try and decide if we should cycle this image
// around or not.
if ((_iCurrent == _cImages-1) && !_fLoopForever)
{
if (_cLoop)
_cLoop --;
// if cLoop is zero then we're done looping
if (_cLoop == 0)
return S_FALSE;
}
// advance to the next image in the sequence
_iCurrent = (_iCurrent+1) % _cImages;
return _GetDisplayedImage();
}
HRESULT CImageData::NextPage()
{
if (_iCurrent >= _cImages-1)
return OLE_E_ENUM_NOMORE;
// Since we are moving to the next page throw away any edits
DiscardEdit();
_iCurrent++;
return _GetDisplayedImage();
}
HRESULT CImageData::PrevPage()
{
if (_iCurrent == 0)
return OLE_E_ENUM_NOMORE;
// Since we are moving to the next page throw away any edits
DiscardEdit();
_iCurrent--;
return _GetDisplayedImage();
}
STDMETHODIMP CImageData::IsTransparent()
{
HRESULT hr = _EnsureImage();
if (SUCCEEDED(hr))
hr = (_pImage->GetFlags() & ImageFlagsHasAlpha) ? S_OK : S_FALSE;
return hr;
}
HRESULT CImageData::IsVector()
{
HRESULT hr = _EnsureImage();
if (SUCCEEDED(hr))
{
hr = (_pImage->GetFlags() & ImageFlagsScalable) ? S_OK : S_FALSE;
}
return hr;
}
HRESULT CImageData::GetSize(SIZE *pSize)
{
HRESULT hr = _EnsureImage();
if (SUCCEEDED(hr))
{
Image *pimg = _pimgEdited ? _pimgEdited : _pImage;
pSize->cx = pimg->GetWidth();
pSize->cy = pimg->GetHeight();
}
return hr;
}
HRESULT CImageData::GetRawDataFormat(GUID *pfmt)
{
HRESULT hr = _EnsureImage();
if (SUCCEEDED(hr))
{
*pfmt = _guidFmt;
}
return hr;
}
HRESULT CImageData::GetPixelFormat(PixelFormat *pfmt)
{
HRESULT hr = _EnsureImage();
if (SUCCEEDED(hr))
{
*pfmt = _pImage->GetPixelFormat();
}
return hr;
}
HRESULT CImageData::GetDelay(DWORD *pdwDelay)
{
HRESULT hr = _EnsureImage();
DWORD dwFrame = _iCurrent;
if (SUCCEEDED(hr))
{
hr = E_FAIL;
if (_piAnimDelay)
{
if (_piAnimDelay->length != (sizeof(DWORD) * _cImages))
{
dwFrame = 0; // if array is not the expected size, be safe and just grab the delay of the first image
}
CopyMemory(pdwDelay, (void *)((UINT_PTR)_piAnimDelay->value + dwFrame * sizeof(DWORD)), sizeof(DWORD));
*pdwDelay = *pdwDelay * 10;
if (*pdwDelay < 100)
{
*pdwDelay = 100; // hack: do the same thing as mshtml, see inetcore\mshtml\src\site\download\imggif.cxx!CImgTaskGif::ReadGIFMaster
}
hr = S_OK;
}
}
return hr;
}
HRESULT CImageData::IsDecoded()
{
return _pImage ? S_OK : S_FALSE;
}
HRESULT CImageData::DisplayName(LPWSTR wszName, UINT cch)
{
HRESULT hr = E_FAIL;
// always set the out parameter to something known
*wszName = L'\0';
if (_pstrm)
{
hr = _pstrm->DisplayName(wszName, cch);
}
// REVIEW: If the user has selected not to view file extentions for known types should we hide the extention?
return hr;
}
// property handling code - decoding, conversion and other packing
HRESULT CImageData::_PropImgToVariant(PropertyItem *pi, VARIANT *pvar)
{
HRESULT hr = S_OK;
switch (pi->type)
{
case PropertyTagTypeByte:
pvar->vt = VT_UI1;
// check for multi-valued property and convert to safearray if found
if (pi->length > sizeof(UCHAR))
{
SAFEARRAYBOUND bound;
bound.cElements = pi->length/sizeof(UCHAR);
bound.lLbound = 0;
pvar->vt |= VT_ARRAY;
hr = E_OUTOFMEMORY;
pvar->parray = SafeArrayCreate(VT_UI1, 1, &bound);
if (pvar->parray)
{
void *pv;
hr = SafeArrayAccessData (pvar->parray, &pv);
if (SUCCEEDED(hr))
{
CopyMemory(pv, pi->value, pi->length);
SafeArrayUnaccessData(pvar->parray);
}
}
}
else
{
pvar->bVal = *((UCHAR*)pi->value);
}
break;
case PropertyTagTypeShort:
pvar->vt = VT_UI2;
pvar->uiVal = *((USHORT*)pi->value);
break;
case PropertyTagTypeLong:
pvar->vt = VT_UI4;
pvar->ulVal = *((ULONG*)pi->value);
break;
case PropertyTagTypeASCII:
{
WCHAR szValue[MAX_PATH];
SHAnsiToUnicode(((LPSTR)pi->value), szValue, ARRAYSIZE(szValue));
hr = InitVariantFromStr(pvar, szValue);
}
break;
case PropertyTagTypeRational:
{
LONG *pl = (LONG*)pi->value;
LONG num = pl[0];
LONG den = pl[1];
pvar->vt = VT_R8;
if (0 == den)
pvar->dblVal = 0; // don't divide by zero
else
pvar->dblVal = ((double)num)/((double)den);
}
break;
case PropertyTagTypeUndefined:
case PropertyTagTypeSLONG:
case PropertyTagTypeSRational:
default:
hr = E_UNEXPECTED;
break;
}
return hr;
}
HRESULT CImageData::_GetProperty(PROPID id, VARIANT *pvar, VARTYPE vt)
{
UINT cb = _pImage->GetPropertyItemSize(id);
HRESULT hr = HR_FROM_STATUS(_pImage->GetLastStatus());
if (cb && SUCCEEDED(hr))
{
PropertyItem *pi = (PropertyItem*)LocalAlloc(LPTR, cb);
if (pi)
{
hr = HR_FROM_STATUS(_pImage->GetPropertyItem(id, cb, pi));
if (SUCCEEDED(hr))
{
hr = _PropImgToVariant(pi, pvar);
}
LocalFree(pi);
}
}
if (SUCCEEDED(hr) && (vt != 0) && (pvar->vt != vt))
hr = VariantChangeType(pvar, pvar, 0, vt);
return hr;
}
HRESULT CImageData::GetProperties(DWORD dwMode, IPropertySetStorage **ppss)
{
HRESULT hr = _EnsureProperties(NULL);
if (SUCCEEDED(hr))
{
hr = QueryInterface(IID_PPV_ARG(IPropertySetStorage, ppss));
}
return hr;
}
HRESULT CImageData::_CreateMemPropSetStorage(IPropertySetStorage **ppss)
{
*ppss = NULL;
ILockBytes *plb;
HRESULT hr = CreateILockBytesOnHGlobal(NULL, TRUE, &plb);
if (SUCCEEDED(hr))
{
IStorage *pstg;
hr = StgCreateDocfileOnILockBytes(plb,
STGM_DIRECT|STGM_READWRITE|STGM_CREATE|STGM_SHARE_EXCLUSIVE, 0, &pstg);
if (SUCCEEDED(hr))
{
hr = pstg->QueryInterface(IID_PPV_ARG(IPropertySetStorage, ppss));
pstg->Release();
}
plb->Release();
}
return hr;
}
// _EnsureProperties returns an in-memory IPropertySetStorage for the current active frame
// For read-only files we may not be able to modify the property set, so handle access issues
// gracefully.
HRESULT CImageData::_EnsureProperties(IPropertySetStorage **ppss)
{
if (ppss)
{
*ppss = NULL;
}
Decode(SHIMGDEC_DEFAULT, 0, 0);
HRESULT hr = _EnsureImage();
if (SUCCEEDED(hr) && !_hdpaProps)
{
_hdpaProps = DPA_Create(_cImages);
if (!_hdpaProps)
{
hr = E_OUTOFMEMORY;
}
}
if (SUCCEEDED(hr))
{
IPropertySetStorage *pss = (IPropertySetStorage*)DPA_GetPtr(_hdpaProps, _iCurrent);
if (!pss)
{
hr = _CreateMemPropSetStorage(&pss);
if (SUCCEEDED(hr))
{
// fill in the NTFS or memory-based FMTID_ImageProperties if it doesn't already exist
IPropertyStorage *pps;
// we use CImagePropset to fill in the propertystorage when it is first created
if (SUCCEEDED(pss->Create(FMTID_ImageProperties, &CLSID_NULL, PROPSETFLAG_DEFAULT, STGM_READWRITE|STGM_SHARE_EXCLUSIVE, &pps)))
{
CImagePropSet *ppsImg = new CImagePropSet(_pImage, NULL, pps, FMTID_ImageProperties);
if (ppsImg)
{
ppsImg->SyncImagePropsToStorage();
ppsImg->Release();
}
pps->Release();
}
if (_guidFmt == ImageFormatJPEG || _guidFmt == ImageFormatTIFF)
{
// for now ignore failures here it's not a catastrophic problem if they aren't written
if (SUCCEEDED(pss->Create(FMTID_SummaryInformation, &CLSID_NULL, PROPSETFLAG_DEFAULT, STGM_FAILIFTHERE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, &pps)))
{
CImagePropSet *ppsSummary = new CImagePropSet(_pImage, NULL, pps, FMTID_SummaryInformation);
if (ppsSummary)
{
ppsSummary->SyncImagePropsToStorage();
ppsSummary->Release();
}
pps->Release();
}
}
DPA_SetPtr(_hdpaProps, _iCurrent, pss);
}
}
if (SUCCEEDED(hr) && ppss)
{
*ppss = pss;
}
}
return hr;
}
// NOTE: ppps is an IN-OUT parameter
HRESULT CImageData::_CreatePropStorage(IPropertyStorage **ppps, REFFMTID fmtid)
{
HRESULT hr = E_FAIL;
if (_pImage)
{
CImagePropSet *ppsImg = new CImagePropSet(_pImage, this, *ppps, fmtid, _PropertyChanged);
ATOMICRELEASE(*ppps);
if (ppsImg)
{
hr = ppsImg->QueryInterface(IID_PPV_ARG(IPropertyStorage, ppps));
ppsImg->Release();
}
else
{
hr = E_OUTOFMEMORY;
}
}
return hr;
}
// IPropertySetStorage
//
// If the caller wants FMTID_ImageProperties use CImagePropSet
//
STDMETHODIMP CImageData::Create(REFFMTID fmtid, const CLSID *pclsid, DWORD grfFlags,
DWORD grfMode, IPropertyStorage **pppropstg)
{
*pppropstg = NULL;
IPropertySetStorage *pss;
HRESULT hr = _EnsureProperties(&pss);
if (SUCCEEDED(hr))
{
if ((S_OK != IsEditable()) && (grfMode & (STGM_READWRITE | STGM_WRITE)))
{
hr = STG_E_ACCESSDENIED;
}
}
if (SUCCEEDED(hr))
{
IPropertyStorage *pps = NULL;
hr = pss->Create(fmtid, pclsid, grfFlags, grfMode, &pps);
if (SUCCEEDED(hr))
{
hr = _CreatePropStorage(&pps, fmtid);
}
*pppropstg = pps;
}
return hr;
}
STDMETHODIMP CImageData::Open(REFFMTID fmtid, DWORD grfMode, IPropertyStorage **pppropstg)
{
*pppropstg = NULL;
IPropertySetStorage *pss;
HRESULT hr = _EnsureProperties(&pss);
if (SUCCEEDED(hr))
{
if ((S_OK != IsEditable()) && (grfMode & (STGM_READWRITE | STGM_WRITE)))
{
hr = STG_E_ACCESSDENIED;
}
}
if (SUCCEEDED(hr))
{
IPropertyStorage *pps = NULL;
// special case FMTID_ImageSummaryInformation...it is readonly and not backed up
// by a real property stream.
if (FMTID_ImageSummaryInformation != fmtid)
{
hr = pss->Open(fmtid, grfMode, &pps);
}
if (SUCCEEDED(hr))
{
hr = _CreatePropStorage(&pps, fmtid);
}
*pppropstg = pps;
}
return hr;
}
STDMETHODIMP CImageData::Delete(REFFMTID fmtid)
{
IPropertySetStorage *pss;
HRESULT hr = _EnsureProperties(&pss);
if (SUCCEEDED(hr))
{
hr = pss->Delete(fmtid);
}
return hr;
}
STDMETHODIMP CImageData::Enum(IEnumSTATPROPSETSTG **ppenum)
{
IPropertySetStorage *pss;
HRESULT hr = E_INVALIDARG;
if (ppenum)
{
hr = _EnsureProperties(&pss);
*ppenum = NULL;
}
if (SUCCEEDED(hr))
{
IEnumSTATPROPSETSTG *pEnum;
hr = pss->Enum(&pEnum);
if (SUCCEEDED(hr))
{
CFmtEnum *pFmtEnum = new CFmtEnum(pEnum);
if (pFmtEnum)
{
hr = pFmtEnum->QueryInterface(IID_PPV_ARG(IEnumSTATPROPSETSTG, ppenum));
pEnum->Release();
pFmtEnum->Release();
}
else
{
*ppenum = pEnum;
}
}
}
return hr;
}
// editing support
void CImageData::_SetEditImage(Image *pimgEdit)
{
if (_pimgEdited)
delete _pimgEdited;
_pimgEdited = pimgEdit;
}
// valid input is 0, 90, 180, or 270
HRESULT CImageData::Rotate(DWORD dwAngle)
{
HRESULT hr = _EnsureImage();
if (SUCCEEDED(hr))
{
// this has bad effects on animated images so don't do it
if (_fAnimated)
return E_NOTVALIDFORANIMATEDIMAGE;
RotateFlipType rft;
switch (dwAngle)
{
case 0:
hr = S_FALSE;
break;
case 90:
rft = Rotate90FlipNone;
break;
case 180:
rft = Rotate180FlipNone;
break;
case 270:
rft = Rotate270FlipNone;
break;
default:
hr = E_INVALIDARG;
}
if (S_OK == hr)
{
// get the current image we have displayed, ready to edit it.
Image * pimg = _pimgEdited ? _pimgEdited->Clone() : _pImage->Clone();
if (pimg)
{
// In order to fix Windows bug #325413 GDIPlus needs to throw away any decoded frames
// in memory for the cloned image. Therefore, we can no longer rely on
// RotateFlip to flip the decoded frame already in memory and must explicitly
// select it into the cloned image before calling RotateFlip to fix Windows Bug #368498
const CLSID * pclsidFrameDim = _fAnimated ? &FrameDimensionTime : &FrameDimensionPage;
hr = HR_FROM_STATUS(pimg->SelectActiveFrame(pclsidFrameDim, _iCurrent));
if (SUCCEEDED(hr))
{
hr = HR_FROM_STATUS(pimg->RotateFlip(rft));
if (SUCCEEDED(hr))
{
_dwRotation = (_dwRotation + dwAngle) % 360;
_SetEditImage(pimg);
}
}
if (FAILED(hr))
{
delete pimg;
}
}
else
{
hr = E_OUTOFMEMORY;
}
}
}
return hr;
}
HRESULT CImageData::Scale(ULONG cx, ULONG cy, InterpolationMode hints)
{
HRESULT hr = _EnsureImage();
if (SUCCEEDED(hr))
{
// this has bad effects on animated images
if (_fAnimated)
return E_NOTVALIDFORANIMATEDIMAGE;
Image * pimg = _pimgEdited ? _pimgEdited : _pImage;
// we have an image, lets determine the new size (preserving aspect ratio
// and ensuring that we don't end up with a 0 sized image as a result.
if (cy == 0)
cy = MulDiv(pimg->GetHeight(), cx, pimg->GetWidth());
else if (cx == 0)
cx = MulDiv(pimg->GetWidth(), cy, pimg->GetHeight());
cx = max(cx, 1);
cy = max(cy, 1);
// construct our new image and draw into it.
Bitmap *pimgNew = new Bitmap(cx, cy);
if (pimgNew)
{
Graphics g(pimgNew);
g.SetInterpolationMode(hints);
hr = HR_FROM_STATUS(g.DrawImage(pimg, Rect(0, 0, cx, cy),
0, 0, pimg->GetWidth(), pimg->GetHeight(),
UnitPixel, NULL, QueryAbort, this));
//
// GDI+ sometimes forgets to tell us it gave up due to an abort.
//
if (SUCCEEDED(hr) && QueryAbort(this))
hr = E_ABORT;
if (SUCCEEDED(hr))
{
pimgNew->SetResolution(pimg->GetHorizontalResolution(), pimg->GetVerticalResolution());
_SetEditImage(pimgNew);
_fDestructive = TRUE; // the edit was Destructive
}
else
{
delete pimgNew;
}
}
else
{
hr = E_OUTOFMEMORY;
}
}
// Suspend the stream so we don't leave the file open
_SuspendStream();
return hr;
}
HRESULT CImageData::DiscardEdit()
{
// NB: The following code is not valid in all cases. For example, if you rotated, then scaled, then rotated again
// this code wouldn't work. We currently don't allow that scenario, so we shouldn't hit this problem, but it
// could be an issue for others using this object so we should figure out what to do about it. This code works
// if: 1.) your first edit is a scale, or 2.) you only do rotates. Also note, this code will clear the "dirty" bit
// so it would prevent the image from being saved, thus the failure of this code won't effect the data on disk.
if (_pimgEdited)
{
delete _pimgEdited;
_pimgEdited = NULL;
}
_dwRotation = 0;
_fDestructive = FALSE;
return S_OK;
}
// handle persisting images
HRESULT CImageData::SetEncoderParams(IPropertyBag *ppbEnc)
{
IUnknown_Set((IUnknown**)&_ppbEncoderParams, ppbEnc);
return S_OK;
}
// save images to the given stream that we have, using the format ID we have
void CImageData::_AddEncParameter(EncoderParameters *pep, GUID guidProperty, ULONG type, void *pv)
{
pep->Parameter[pep->Count].Guid = guidProperty;
pep->Parameter[pep->Count].Type = type;
pep->Parameter[pep->Count].NumberOfValues = 1;
pep->Parameter[pep->Count].Value = pv;
pep->Count++;
}
#define MAX_ENC_PARAMS 3
HRESULT CImageData::_SaveImages(IStream *pstrm, GUID * pguidFmt)
{
HRESULT hr = S_OK;
int iQuality = 0; // == 0 is a special case
// did the encoder specify a format for us to save in?
ASSERTMSG(NULL != pguidFmt, "Invalid pguidFmt passed to internal function CImageData::_SaveImages");
GUID guidFmt = *pguidFmt;
if (_ppbEncoderParams)
{
VARIANT var = {0};
// read the encoder format to be used
if (SUCCEEDED(_ppbEncoderParams->Read(SHIMGKEY_RAWFORMAT, &var, NULL)))
{
VariantToGUID(&var, &guidFmt);
VariantClear(&var);
}
// read the encoder quality to be used, this is set for the JPEG one only
if (guidFmt == ImageFormatJPEG)
{
SHPropertyBag_ReadInt(_ppbEncoderParams, SHIMGKEY_QUALITY, &iQuality);
iQuality = max(0, iQuality);
iQuality = min(100, iQuality);
}
}
// given the format GUID lets determine the encoder we intend to
// use to save the image
CLSID clsidEncoder;
hr = _GetEncoderFromFormat(&guidFmt, &clsidEncoder);
if (SUCCEEDED(hr))
{
// the way encoding works with GDI+ is a bit strange, you first need to call an image to
// have it save into a particular stream/file. if the image is multi-page then you
// must set an encoder parameter which defines that this will be a multi-page save (and
// that you will be calling the SaveAdd later).
//
// having performed the initial save, you must then attempt to add the subsequent pages
// to the file by calling SaveAdd, you call that method on the first image you saved
// specifying that you are adding another page (and possibly that this is the last image
// in the series).
BOOL bSaveCurrentOnly = FALSE;
Image *pimgFirstSave = NULL;
DWORD dwMaxPage = _cImages;
DWORD dwMinPage = 0;
// If viewing a multipage image and saving to a single page format, only save the current frame
if (_cImages > 1 && !FmtSupportsMultiPage(this, &guidFmt))
{
bSaveCurrentOnly = TRUE;
dwMaxPage = _iCurrent+1;
dwMinPage = _iCurrent;
}
for (DWORD i = dwMinPage; SUCCEEDED(hr) && (i < dwMaxPage); i++)
{
EncoderParameters ep[MAX_ENC_PARAMS] = { 0 };
ULONG ulCompression = 0; // in same scope as ep
// we use _pImage as the source if unedited in order to preserve properties
const CLSID * pclsidFrameDim = _fAnimated ? &FrameDimensionTime : &FrameDimensionPage;
_pImage->SelectActiveFrame(pclsidFrameDim, i);
Image *pimg;
if (_pimgEdited && i==_iCurrent)
{
pimg = _pimgEdited;
}
else
{
pimg = _pImage;
}
_SaveFrameProperties(pimg, i);
if (guidFmt == ImageFormatTIFF)
{
VARIANT var = {0};
if (SUCCEEDED(_GetProperty(PropertyTagCompression, &var, VT_UI2)))
{
// be sure to preserve TIFF compression
// these values are taken from the TIFF spec
switch (var.uiVal)
{
case 1:
ulCompression = EncoderValueCompressionNone;
break;
case 2:
ulCompression = EncoderValueCompressionCCITT3;
break;
case 3:
ulCompression = EncoderValueCompressionCCITT4;
break;
case 5:
ulCompression = EncoderValueCompressionLZW;
break;
case 32773:
ulCompression = EncoderValueCompressionRle;
break;
default:
// use the GDI+ default
break;
}
VariantClear(&var);
if (ulCompression)
{
_AddEncParameter(ep, EncoderCompression, EncoderParameterValueTypeLong, &ulCompression);
}
}
}
if (i == dwMinPage)
{
// we are writing the first page of the image, if this is a multi-page
// image then we need to set the encoder parameters accordingly (eg. set to
// multi-page).
ULONG ulValue = 0; // This needs to be in scope when Save is called
// We can only to lossless rotation when:
// * The original image is a JPEG file
// * The destination image is a JPEG file
// * We are only rotating and not scaling
// * The width and height of the JPEG are multiples of 8
// * Quality is unchanged by the caller
if (!_fDestructive &&
IsEqualIID(_guidFmt, ImageFormatJPEG) &&
IsEqualIID(guidFmt, ImageFormatJPEG) &&
(iQuality == 0))
{
// this code assumes JPEG files are single page since it's inside the i==0 case
ASSERT(_cImages == 1);
// for JPEG when doing only a rotate we use a special encoder parameter on the original
// image rather than using the edit image. This allows lossless rotation.
pimg = _pImage;
switch (_dwRotation)
{
case 90:
ulValue = EncoderValueTransformRotate90;
break;
case 180:
ulValue = EncoderValueTransformRotate180;
break;
case 270:
ulValue = EncoderValueTransformRotate270;
break;
}
_AddEncParameter(ep, EncoderTransformation, EncoderParameterValueTypeLong, &ulValue);
}
else if (_cImages > 1 && !bSaveCurrentOnly)
{
ulValue = EncoderValueMultiFrame;
_AddEncParameter(ep, EncoderSaveFlag, EncoderParameterValueTypeLong, &ulValue);
pimgFirstSave = pimg; // keep this image as we will us it for appending pages
}
// JPEG quality is only ever set for a single image, therefore don't
// bother passing it for the multi page case.
if (iQuality > 0)
_AddEncParameter(ep, EncoderQuality, EncoderParameterValueTypeLong, &iQuality);
hr = HR_FROM_STATUS(pimg->Save(pstrm, &clsidEncoder, (ep->Count > 0) ? ep:NULL));
}
else
{
// writing the next image in the series, set the encoding parameter
// to indicate that this is the next page. if we are writing the last
// image then set the last frame flag.
ULONG flagValueDim = EncoderValueFrameDimensionPage;
ULONG flagValueLastFrame = EncoderValueLastFrame;
_AddEncParameter(ep, EncoderSaveFlag, EncoderParameterValueTypeLong, &flagValueDim);
if (i == (dwMaxPage-1))
_AddEncParameter(ep, EncoderSaveFlag, EncoderParameterValueTypeLong, &flagValueLastFrame);
hr = HR_FROM_STATUS(pimgFirstSave->SaveAdd(pimg, (ep->Count > 0) ? ep:NULL));
}
}
}
if (SUCCEEDED(hr))
{
_fPropertyChanged = FALSE;
DiscardEdit();
}
// Suspend the stream so we don't leave the file open
_SuspendStream();
return hr;
}
// returns the DPI of the image
STDMETHODIMP CImageData::GetResolution(ULONG *puResolutionX, ULONG *puResolutionY)
{
if (!puResolutionX && !puResolutionY)
{
return E_INVALIDARG;
}
HRESULT hr = _EnsureImage();
if (puResolutionX)
{
*puResolutionX = 0;
}
if (puResolutionY)
{
*puResolutionY = 0;
}
if (SUCCEEDED(hr))
{
UINT uFlags = _pImage->GetFlags();
//
// We only return the DPI information from the image header for TIFFs whose
// X and Y DPI differ, those images are likely faxes.
// We want our client applications (slideshow, image preview)
// to deal with actual pixel sizes for the most part
//
ULONG resX = (ULONG)_pImage->GetHorizontalResolution();
ULONG resY = (ULONG)_pImage->GetVerticalResolution();
#ifndef USE_EMBEDDED_DPI_ALWAYS
if (_guidFmt != ImageFormatTIFF || !(uFlags & ImageFlagsHasRealDPI) || resX == resY )
{
// if GetDC fails we have to rely on the numbers back from GDI+
HDC hdc = GetDC(NULL);
if (hdc)
{
resX = GetDeviceCaps(hdc, LOGPIXELSX);
resY = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(NULL, hdc);
}
}
#endif
if (puResolutionX)
{
*puResolutionX = resX;
}
if (puResolutionY)
{
*puResolutionY = resY;
}
if ((puResolutionX && !*puResolutionX) || (puResolutionY && !*puResolutionY))
{
hr = E_FAIL;
}
}
return hr;
}
// handle saving and replacing the original file
// in the case of replacing an existing file, we want the temp file to be in the same volume
// as the target
HRESULT CImageData::_MakeTempFile(LPWSTR pszFile)
{
ASSERT(_pstrm);
WCHAR szTempPath[MAX_PATH];
HRESULT hr = S_OK;
if (_pstrm->IsFileStream())
{
StrCpyN(szTempPath, _pstrm->GetFilename(), ARRAYSIZE(szTempPath));
PathRemoveFileSpec(szTempPath);
}
else if (!GetTempPath(ARRAYSIZE(szTempPath), szTempPath))
{
hr = E_FAIL;
}
if (SUCCEEDED(hr))
{
// SIV == "Shell Image Viewer"
if (GetTempFileName(szTempPath, TEXT("SIV"), 0, pszFile))
{
SetFileAttributes(pszFile, ATTRIBUTES_TEMPFILE);
// we need to suppress the change notfy from the GetTempFileName()
// call as that causes defview to display this.
// but for some reason it does not work
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, pszFile, NULL);
}
else
{
hr = E_FAIL;
}
}
return hr;
}
HRESULT CImageData::_ReplaceFile(LPCTSTR pszNewFile)
{
// first we get some info about the file we're replacing:
LPCTSTR pszOldFile = _pstrm->GetFilename();
STATSTG ss = {0};
_pstrm->Stat(&ss, STATFLAG_NONAME);
// This ensures that the source handle is closed
_SuspendStream();
HRESULT hr;
// ReplaceFile doesn't save the modified time, so if we rotate an image twice in quick succession
// we won't add a full 2 seconds to the modified time. So query the time before replacing the file.
WIN32_FIND_DATA wfd = {0};
GetFileAttributesEx(pszOldFile, GetFileExInfoStandard, &wfd);
if (ReplaceFile(pszOldFile, pszNewFile, NULL, REPLACEFILE_WRITE_THROUGH, NULL, NULL))
{
// The old file has been replaced with the new file, but now we need to ensure that the
// filetime actually changed due to the 2 sec accuracy of FAT.
// we do this on NTFS too, since XP pidls have 2 sec accuracy since they cast the filetime
// down to a dos datetime.
HANDLE hFile = CreateFile(pszOldFile, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
FILETIME *pft = (CompareFileTime(&wfd.ftLastWriteTime, &ss.mtime) < 0) ? &ss.mtime : &wfd.ftLastWriteTime;
IncrementFILETIME(pft, 2 * FT_ONESECOND);
SetFileTime(hFile, NULL, NULL, pft);
CloseHandle(hFile);
}
// the replacefile call wont always keep the "replaced" file (pszOldFile) attributes, if it's
// replacing across a win98 share for example. no biggie, just set the attributes again, using
// the attribs we know we got from the stat.
SetFileAttributes(pszOldFile, ss.reserved);
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT | SHCNF_FLUSH, pszOldFile, NULL);
hr = S_OK;
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
return hr;
}
void SaveProperties(IPropertySetStorage *pss, Image *pimg, REFFMTID fmtid, CDSA<SHCOLUMNID> *pdsaChanges)
{
IPropertyStorage *pps;
if (SUCCEEDED(pss->Open(fmtid, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, &pps)))
{
CImagePropSet *pips = new CImagePropSet(pimg, NULL, pps, fmtid);
if (pips)
{
pips->SaveProps(pimg, pdsaChanges);
pips->Release();
}
pps->Release();
}
}
void CImageData::_SaveFrameProperties(Image *pimg, LONG iFrame)
{
// make sure _dsaChangedProps is non-NULL
if (_hdpaProps && (HDSA)_dsaChangedProps)
{
IPropertySetStorage *pss = (IPropertySetStorage *)DPA_GetPtr(_hdpaProps, iFrame);
if (pss)
{
// Start with FMTID_ImageProperties to make sure other FMTIDs take precedence (last one wins)
SaveProperties(pss, pimg, FMTID_ImageProperties, &_dsaChangedProps);
// enum all the property storages and create a CImagePropSet for each one
// and have it save to the pimg
IEnumSTATPROPSETSTG *penum;
if (SUCCEEDED(pss->Enum(&penum)))
{
STATPROPSETSTG spss;
while (S_OK == penum->Next(1, &spss, NULL))
{
if (!IsEqualGUID(spss.fmtid, FMTID_ImageProperties))
{
SaveProperties(pss, pimg, spss.fmtid, &_dsaChangedProps);
}
}
penum->Release();
}
}
}
}
void CImageData::_PropertyChanged(IShellImageData* pThis, SHCOLUMNID *pscid)
{
((CImageData*)pThis)->_fPropertyChanged = TRUE;
if ((HDSA)(((CImageData*)pThis)->_dsaChangedProps))
{
((CImageData*)pThis)->_dsaChangedProps.AppendItem(pscid);
}
}
//
// This function determines the list of available encoder parameters given the file format
// Hopefully future versions of GDI+ will decouple this call from the Image() object
// Don't call this function until ready to save the loaded image
STDMETHODIMP CImageData::GetEncoderParams(GUID *pguidFmt, EncoderParameters **ppencParams)
{
CLSID clsidEncoder;
HRESULT hr = E_FAIL;
if (_pImage && ppencParams)
{
hr = _GetEncoderFromFormat(pguidFmt, &clsidEncoder);
}
if (SUCCEEDED(hr))
{
hr = E_FAIL;
UINT uSize = _pImage->GetEncoderParameterListSize(&clsidEncoder);
if (uSize)
{
*ppencParams = (EncoderParameters *)CoTaskMemAlloc(uSize);
if (*ppencParams)
{
hr = HR_FROM_STATUS(_pImage->GetEncoderParameterList(&clsidEncoder, uSize, *ppencParams));
if (FAILED(hr))
{
CoTaskMemFree(*ppencParams);
*ppencParams = NULL;
}
}
}
}
return hr;
}
STDMETHODIMP CImageData::RegisterAbort(IShellImageDataAbort *pAbort, IShellImageDataAbort **ppAbortPrev)
{
if (ppAbortPrev)
{
*ppAbortPrev = _pAbort; // Transfer ownership to caller
}
else if (_pAbort)
{
_pAbort->Release(); // Caller doesn't want it, so throw away
}
_pAbort = pAbort; // Set the new abort callback
if (_pAbort)
{
_pAbort->AddRef();
}
return S_OK;
}
BOOL CALLBACK CImageData::QueryAbort(void *pvRef)
{
CImageData* pThis = reinterpret_cast<CImageData *>(pvRef);
return pThis->_pAbort && pThis->_pAbort->QueryAbort() == S_FALSE;
}
HRESULT CImageData::CloneFrame(Image **ppimg)
{
*ppimg = NULL;
Image *pimg = _pimgEdited ? _pimgEdited : _pImage;
if (pimg)
{
*ppimg = pimg->Clone();
}
return *ppimg ? S_OK : E_FAIL;
}
HRESULT CImageData::ReplaceFrame(Image *pimg)
{
_SetEditImage(pimg);
return S_OK;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// CImageDataFactory
/////////////////////////////////////////////////////////////////////////////////////////////////
STDAPI CImageDataFactory_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
{
CImageFactory *psid = new CImageFactory();
if (!psid)
{
*ppunk = NULL; // incase of failure
return E_OUTOFMEMORY;
}
HRESULT hr = psid->QueryInterface(IID_PPV_ARG(IUnknown, ppunk));
psid->Release();
return hr;
}
CImageFactory::CImageFactory() : _cRef(1)
{
_Module.Lock();
}
CImageFactory::~CImageFactory()
{
_Module.Unlock();
}
STDMETHODIMP CImageFactory::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CImageFactory, IShellImageDataFactory),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG) CImageFactory::AddRef()
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_(ULONG) CImageFactory::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
HRESULT CImageFactory::CreateIShellImageData(IShellImageData **ppshimg)
{
CImageData *psid = new CImageData();
if (!psid)
return E_OUTOFMEMORY;
HRESULT hr = psid->QueryInterface(IID_PPV_ARG(IShellImageData, ppshimg));
psid->Release();
return hr;
}
HRESULT CImageFactory::CreateImageFromFile(LPCWSTR pszPath, IShellImageData **ppshimg)
{
HRESULT hr = E_OUTOFMEMORY;
CImageData *psid = new CImageData();
if (psid)
{
IPersistFile *ppf;
hr = psid->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
if (SUCCEEDED(hr))
{
hr = ppf->Load(pszPath, STGM_READ);
ppf->Release();
}
if (SUCCEEDED(hr))
hr = psid->QueryInterface(IID_PPV_ARG(IShellImageData, ppshimg));
psid->Release();
}
return hr;
}
HRESULT CImageFactory::CreateImageFromStream(IStream *pstrm, IShellImageData **ppshimg)
{
HRESULT hr = E_OUTOFMEMORY;
CImageData *psid = new CImageData();
if (psid)
{
IPersistStream *ppstrm;
hr = psid->QueryInterface(IID_PPV_ARG(IPersistStream, &ppstrm));
if (SUCCEEDED(hr))
{
hr = ppstrm->Load(pstrm);
ppstrm->Release();
}
if (SUCCEEDED(hr))
hr = psid->QueryInterface(IID_PPV_ARG(IShellImageData, ppshimg));
psid->Release();
}
return hr;
}
HRESULT CImageFactory::GetDataFormatFromPath(LPCWSTR pszPath, GUID *pguidFmt)
{
return _GetDataFormatFromPath(pszPath, pguidFmt);
}
HRESULT CEncoderInfo::_GetDataFormatFromPath(LPCWSTR pszPath, GUID *pguidFmt)
{
*pguidFmt = GUID_NULL;
HRESULT hr = _GetEncoderList();
if (SUCCEEDED(hr))
{
UINT i = FindInDecoderList(_pici, _cEncoders, pszPath);
if (-1 != i)
{
*pguidFmt = _pici[i].FormatID;
hr = S_OK;
}
else
{
hr = E_FAIL;
}
}
return hr;
}
HRESULT CEncoderInfo::_GetEncoderList()
{
HRESULT hr = S_OK;
if (!_pici)
{
// lets pick up the list of encoders, first we get the encoder size which
// gives us the CB and the number of encoders that are installed on the
// machine.
UINT cb;
hr = HR_FROM_STATUS(GetImageEncodersSize(&_cEncoders, &cb));
if (SUCCEEDED(hr))
{
// allocate the buffer for the encoders and then fill it
// with the encoder list.
_pici = (ImageCodecInfo*)LocalAlloc(LPTR, cb);
if (_pici)
{
hr = HR_FROM_STATUS(GetImageEncoders(_cEncoders, cb, _pici));
if (FAILED(hr))
{
LocalFree(_pici);
_pici = NULL;
}
}
else
{
hr = E_OUTOFMEMORY;
}
}
}
return hr;
}
HRESULT CEncoderInfo::_GetEncoderFromFormat(const GUID *pfmt, CLSID *pclsidEncoder)
{
HRESULT hr = _GetEncoderList();
if (SUCCEEDED(hr))
{
hr = E_FAIL;
for (UINT i = 0; i != _cEncoders; i++)
{
if (_pici[i].FormatID == *pfmt)
{
if (pclsidEncoder)
{
*pclsidEncoder = _pici[i].Clsid; // return the CLSID of the encoder so we can create again
}
hr = S_OK;
break;
}
}
}
return hr;
}
CEncoderInfo::CEncoderInfo()
{
_pici = NULL;
_cEncoders = 0;
}
CEncoderInfo::~CEncoderInfo()
{
if (_pici)
LocalFree(_pici); // do we have an encoder array to be destroyed
}
STDAPI CImageData_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
{
CImageData *psid = new CImageData(*(poi->pclsid) == CLSID_ImagePropertyHandler);
if (!psid)
{
*ppunk = NULL; // incase of failure
return E_OUTOFMEMORY;
}
HRESULT hr = psid->QueryInterface(IID_PPV_ARG(IUnknown, ppunk));
psid->Release();
return hr;
}
int CImageData::_FreeProps(void* pProp, void* pData)
{
if (pProp)
{
((IPropertySetStorage*)pProp)->Release();
}
return 1;
}
// Our CFmtEnum is a minimal enumerator to provide FMTID_SummaryInformation in our
// formats. It's optimized for 1-by-1 enumeration
STDMETHODIMP CFmtEnum::Next(ULONG celt, STATPROPSETSTG *rgelt, ULONG *pceltFetched)
{
HRESULT hr = S_OK;
if (pceltFetched)
{
*pceltFetched = 0;
}
if (!celt || !rgelt)
{
hr = E_INVALIDARG;
}
else if (0 == _idx)
{
ZeroMemory(rgelt, sizeof(*rgelt));
rgelt->fmtid = FMTID_ImageSummaryInformation;
rgelt->grfFlags = STGM_READ | STGM_SHARE_DENY_NONE;
if (pceltFetched)
{
*pceltFetched = 1;
}
_idx++;
celt--;
rgelt++;
}
if (SUCCEEDED(hr) && celt)
{
ULONG ul;
hr = _pEnum->Next(celt, rgelt, &ul);
if (SUCCEEDED(hr) && pceltFetched)
{
(*pceltFetched) += ul;
}
}
return hr;
}
STDMETHODIMP CFmtEnum::Skip(ULONG celt)
{
HRESULT hr = S_OK;
if (_idx == 0)
{
_idx++;
celt--;
}
if (celt)
{
hr = _pEnum->Skip(celt);
}
return hr;
}
STDMETHODIMP CFmtEnum::Reset(void)
{
_idx = 0;
return _pEnum->Reset();
}
STDMETHODIMP CFmtEnum::Clone(IEnumSTATPROPSETSTG **ppenum)
{
HRESULT hr = E_OUTOFMEMORY;
CFmtEnum *pNew = new CFmtEnum(_pEnum);
if (pNew)
{
hr = pNew->QueryInterface(IID_PPV_ARG(IEnumSTATPROPSETSTG, ppenum));
pNew->Release();
}
return hr;
}
STDMETHODIMP CFmtEnum::QueryInterface(REFIID riid, void **ppvObj)
{
static const QITAB qit[] =
{
QITABENT(CFmtEnum, IEnumSTATPROPSETSTG),
{ 0 },
};
return QISearch(this, qit, riid, ppvObj);
}
STDMETHODIMP_(ULONG) CFmtEnum::AddRef()
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_(ULONG) CFmtEnum::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
CFmtEnum::CFmtEnum(IEnumSTATPROPSETSTG *pEnum) : _cRef(1), _idx(0), _pEnum(pEnum)
{
_pEnum->AddRef();
}
CFmtEnum::~CFmtEnum()
{
_pEnum->Release();
}