You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2599 lines
79 KiB
2599 lines
79 KiB
#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()
|
|
{
|
|
ASSERT( 0 != _cRef );
|
|
ULONG cRef = InterlockedDecrement(&_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
// *** 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()
|
|
{
|
|
ASSERT( 0 != _cRef );
|
|
ULONG cRef = InterlockedDecrement(&_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
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);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
_fPropertyChanged = FALSE;
|
|
DiscardEdit();
|
|
}
|
|
}
|
|
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))
|
|
{
|
|
_fPropertyChanged = FALSE;
|
|
DiscardEdit(); // note we can't discard any edits until the original was overwritten
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 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()
|
|
{
|
|
ASSERT( 0 != _cRef );
|
|
ULONG cRef = InterlockedDecrement(&_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
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()
|
|
{
|
|
ASSERT( 0 != _cRef );
|
|
ULONG cRef = InterlockedDecrement(&_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
CFmtEnum::CFmtEnum(IEnumSTATPROPSETSTG *pEnum) : _cRef(1), _idx(0), _pEnum(pEnum)
|
|
{
|
|
_pEnum->AddRef();
|
|
}
|
|
|
|
CFmtEnum::~CFmtEnum()
|
|
{
|
|
_pEnum->Release();
|
|
}
|