#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();
}