Source code of Windows XP (NT5)
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.

2592 lines
76 KiB

  1. #include "precomp.h"
  2. #include <runtask.h>
  3. #include "imagprop.h"
  4. #include "shutil.h"
  5. #pragma hdrstop
  6. #define TF_SUSPENDRESUME 0 // turn on to debug CDecodeStream::Suspend/Resume
  7. #define PF_NOSUSPEND 0 // disable suspend and resume (for debugging purposes)
  8. class CDecodeStream;
  9. ////////////////////////////////////////////////////////////////////////////
  10. class CEncoderInfo
  11. {
  12. public:
  13. CEncoderInfo();
  14. virtual ~CEncoderInfo();
  15. protected:
  16. HRESULT _GetDataFormatFromPath(LPCWSTR pszPath, GUID *pguidFmt);
  17. HRESULT _GetEncoderList();
  18. HRESULT _GetEncoderFromFormat(const GUID *pfmt, CLSID *pclsidEncoder);
  19. UINT _cEncoders; // number of encoders discovered
  20. ImageCodecInfo *_pici; // array of image encoder classes
  21. };
  22. class CImageFactory : public IShellImageDataFactory, private CEncoderInfo,
  23. public NonATLObject
  24. {
  25. public:
  26. // IUnknown
  27. STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
  28. STDMETHODIMP_(ULONG) AddRef();
  29. STDMETHODIMP_(ULONG) Release();
  30. // IShellImageDataFactory
  31. STDMETHODIMP CreateIShellImageData(IShellImageData **ppshimg);
  32. STDMETHODIMP CreateImageFromFile(LPCWSTR pszPath, IShellImageData **ppshimg);
  33. STDMETHODIMP CreateImageFromStream(IStream *pStream, IShellImageData **ppshimg);
  34. STDMETHODIMP GetDataFormatFromPath(LPCWSTR pszPath, GUID *pDataFormat);
  35. CImageFactory();
  36. private:
  37. ~CImageFactory();
  38. LONG _cRef;
  39. CGraphicsInit _cgi;
  40. };
  41. class CImageData : public IShellImageData, IPersistFile, IPersistStream, IPropertySetStorage, private CEncoderInfo,
  42. public NonATLObject
  43. {
  44. public:
  45. CImageData(BOOL fPropertyOnly = FALSE);
  46. static BOOL CALLBACK QueryAbort(void *pvRef);
  47. // IUnknown
  48. STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
  49. STDMETHOD_(ULONG, AddRef)();
  50. STDMETHOD_(ULONG, Release)();
  51. // IPersist
  52. STDMETHOD(GetClassID)(CLSID *pclsid)
  53. { *pclsid = CLSID_ShellImageDataFactory; return S_OK; }
  54. // IPersistFile
  55. STDMETHODIMP IsDirty();
  56. STDMETHODIMP Load(LPCOLESTR pszFileName, DWORD dwMode);
  57. STDMETHODIMP Save(LPCOLESTR pszFileName, BOOL fRemember);
  58. STDMETHODIMP SaveCompleted(LPCOLESTR pszFileName);
  59. STDMETHODIMP GetCurFile(LPOLESTR *ppszFileName);
  60. // IPersistStream
  61. STDMETHOD(Load)(IStream *pstm);
  62. STDMETHOD(Save)(IStream *pstm, BOOL fClearDirty);
  63. STDMETHOD(GetSizeMax)(ULARGE_INTEGER *pcbSize)
  64. { return E_NOTIMPL; }
  65. // IPropertySetStorage methods
  66. STDMETHODIMP Create(REFFMTID fmtid, const CLSID * pclsid, DWORD grfFlags, DWORD grfMode, IPropertyStorage** ppPropStg);
  67. STDMETHODIMP Open(REFFMTID fmtid, DWORD grfMode, IPropertyStorage** ppPropStg);
  68. STDMETHODIMP Delete(REFFMTID fmtid);
  69. STDMETHODIMP Enum(IEnumSTATPROPSETSTG** ppenum);
  70. // IShellImageData
  71. STDMETHODIMP Decode(DWORD dwFlags, ULONG pcx, ULONG pcy);
  72. STDMETHODIMP Draw(HDC hdc, LPRECT prcDest, LPRECT prcSrc);
  73. STDMETHODIMP NextFrame();
  74. STDMETHODIMP NextPage();
  75. STDMETHODIMP PrevPage();
  76. STDMETHODIMP IsTransparent();
  77. STDMETHODIMP IsVector();
  78. STDMETHODIMP IsAnimated()
  79. { return _fAnimated ? S_OK : S_FALSE; }
  80. STDMETHODIMP IsMultipage()
  81. { return (!_fAnimated && _cImages > 1) ? S_OK : S_FALSE; }
  82. STDMETHODIMP IsDecoded();
  83. STDMETHODIMP IsPrintable()
  84. { return S_OK; } // all images are printable
  85. STDMETHODIMP IsEditable()
  86. { return _fEditable ? S_OK : S_FALSE; }
  87. STDMETHODIMP GetCurrentPage(ULONG *pnPage)
  88. { *pnPage = _iCurrent; return S_OK; }
  89. STDMETHODIMP GetPageCount(ULONG *pcPages)
  90. { HRESULT hr = _EnsureImage(); *pcPages = _cImages; return hr; }
  91. STDMETHODIMP SelectPage(ULONG iPage);
  92. STDMETHODIMP GetResolution(ULONG *puResolutionX, ULONG *puResolutionY);
  93. STDMETHODIMP GetRawDataFormat(GUID *pfmt);
  94. STDMETHODIMP GetPixelFormat(PixelFormat *pfmt);
  95. STDMETHODIMP GetSize(SIZE *pSize);
  96. STDMETHODIMP GetDelay(DWORD *pdwDelay);
  97. STDMETHODIMP DisplayName(LPWSTR wszName, UINT cch);
  98. STDMETHODIMP GetProperties(DWORD dwMode, IPropertySetStorage **ppPropSet);
  99. STDMETHODIMP Rotate(DWORD dwAngle);
  100. STDMETHODIMP Scale(ULONG cx, ULONG cy, InterpolationMode hints);
  101. STDMETHODIMP DiscardEdit();
  102. STDMETHODIMP SetEncoderParams(IPropertyBag *ppbEnc);
  103. STDMETHODIMP GetEncoderParams(GUID *pguidFmt, EncoderParameters **ppencParams);
  104. STDMETHODIMP RegisterAbort(IShellImageDataAbort *pAbort, IShellImageDataAbort **ppAbortPrev);
  105. STDMETHODIMP CloneFrame(Image **ppimg);
  106. STDMETHODIMP ReplaceFrame(Image *pimg);
  107. private:
  108. CGraphicsInit _cgi;
  109. LONG _cRef;
  110. DWORD _dwMode; // open mode from IPersistFile::Load()
  111. CDecodeStream *_pstrm; // stream that will produce our data
  112. BOOL _fLoaded; // true once PersistFile or PersistStream have been called
  113. BOOL _fDecoded; // true once Decode ahs been called
  114. DWORD _dwFlags; // flags and size passed to Decode method
  115. int _cxDesired;
  116. int _cyDesired;
  117. Image *_pImage; // source of the images (created from the filename)
  118. // REVIEW: do we need to make these be per-frame/page?
  119. // YES!
  120. Image *_pimgEdited; // edited image
  121. HDPA _hdpaProps; // properties for each frame
  122. DWORD _dwRotation;
  123. BOOL _fDestructive; // not a lossless edit operation
  124. BOOL _fAnimated; // this is an animated stream (eg. not multi page picture)
  125. BOOL _fLoopForever; // loop the animated gif forever
  126. int _cLoop; // loop count (0 forever, n = repeat count)
  127. BOOL _fEditable; // can be edited
  128. GUID _guidFmt; // format GUID (original stream is this)
  129. DWORD _cImages; // number of frames/pages in the image
  130. DWORD _iCurrent; // current frame/page we want to display
  131. PropertyItem *_piAnimDelay; // array of the delay assocated with each frame
  132. BOOL _fPropertyOnly;
  133. BOOL _fPropertyChanged;
  134. // image encoder information (created on demand)
  135. IPropertyBag *_ppbEncoderParams; // property bag with encoder parameters
  136. IShellImageDataAbort *_pAbort; // optional abort callback
  137. CDSA<SHCOLUMNID> _dsaChangedProps; // which properties have changed
  138. private:
  139. ~CImageData();
  140. HRESULT _EnsureImage();
  141. HRESULT _SuspendStream();
  142. HRESULT _SetDecodeStream(CDecodeStream *pds);
  143. HRESULT _CreateMemPropSetStorage(IPropertySetStorage **ppss);
  144. HRESULT _PropImgToVariant(PropertyItem *pi, VARIANT *pvar);
  145. HRESULT _GetProperty(PROPID id, VARIANT *pvar, VARTYPE vt);
  146. HRESULT _GetDisplayedImage();
  147. void _SetEditImage(Image *pimgEdit);
  148. HRESULT _SaveImages(IStream *pstrm, GUID * pguidFmt);
  149. HRESULT _ReplaceFile(LPCTSTR pszNewFile);
  150. HRESULT _MakeTempFile(LPWSTR pszFile);
  151. void _AddEncParameter(EncoderParameters *pep, GUID guidProperty, ULONG type, void *pv);
  152. HRESULT _EnsureProperties(IPropertySetStorage **ppss);
  153. HRESULT _CreatePropStorage(IPropertyStorage **ppps, REFFMTID fmtid);
  154. static int _FreeProps(void *pProp, void *pData);
  155. //
  156. // since CImagePropSet objects come and go, we need to persist which properties need updating in the CImageData
  157. //
  158. void _SaveFrameProperties(Image *pimg, LONG iFrame);
  159. static void _PropertyChanged(IShellImageData *pThis, SHCOLUMNID *pscid );
  160. };
  161. ////////////////////////////////////////////////////////////////////////////
  162. //
  163. // CDecodeStream
  164. //
  165. // Wraps a regular IStream, but is cancellable and can be
  166. // suspended/resumed to prevent the underlying file from being held
  167. // open unnecessarily.
  168. //
  169. ////////////////////////////////////////////////////////////////////////////
  170. class CDecodeStream : public IStream, public NonATLObject
  171. {
  172. public:
  173. CDecodeStream(CImageData *pid, IStream *pstrm);
  174. CDecodeStream(CImageData *pid, LPCTSTR pszFilename, DWORD dwMode);
  175. ~CDecodeStream()
  176. {
  177. ASSERT(!_pidOwner);
  178. #ifdef DEBUG // Need #ifdef because we call a function
  179. if (IsFileStream())
  180. {
  181. TraceMsg(TF_SUSPENDRESUME, "ds.Release %s", PathFindFileName(_szFilename));
  182. }
  183. #endif
  184. ATOMICRELEASE(_pstrmInner);
  185. }
  186. HRESULT Suspend();
  187. HRESULT Resume(BOOL fFullLoad = FALSE);
  188. void Reload();
  189. //
  190. // Before releasing, you must Detach to break the backreference.
  191. // Otherwise, the next time somebody calls QueryCancel, we will fault.
  192. //
  193. void Detach()
  194. {
  195. _pidOwner = NULL;
  196. }
  197. BOOL IsFileStream() { return _szFilename[0]; }
  198. LPCTSTR GetFilename() { return _szFilename; }
  199. HRESULT DisplayName(LPWSTR wszName, UINT cch);
  200. // *** IUnknown ***
  201. STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj);
  202. STDMETHODIMP_(ULONG) AddRef(void);
  203. STDMETHODIMP_(ULONG) Release(void);
  204. // *** IStream ***
  205. STDMETHODIMP Read(void *pv, ULONG cb, ULONG *pcbRead);
  206. STDMETHODIMP Write(void const *pv, ULONG cb, ULONG *pcbWritten);
  207. STDMETHODIMP Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition);
  208. STDMETHODIMP SetSize(ULARGE_INTEGER libNewSize);
  209. STDMETHODIMP CopyTo(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten);
  210. STDMETHODIMP Commit(DWORD grfCommitFlags);
  211. STDMETHODIMP Revert();
  212. STDMETHODIMP LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType);
  213. STDMETHODIMP UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType);
  214. STDMETHODIMP Stat(STATSTG *pstatstg, DWORD grfStatFlag);
  215. STDMETHODIMP Clone(IStream **ppstm);
  216. private:
  217. void CommonConstruct(CImageData *pid) { _cRef = 1; _pidOwner = pid; _fSuspendable = !(g_dwPrototype & PF_NOSUSPEND);}
  218. HRESULT FilterAccess();
  219. private:
  220. IStream * _pstrmInner;
  221. CImageData *_pidOwner; // NOT REFCOUNTED
  222. LONG _cRef;
  223. LARGE_INTEGER _liPos; // Where we were in the file when we suspended
  224. TCHAR _szFilename[MAX_PATH]; // file we are a stream for
  225. BOOL _fSuspendable;
  226. };
  227. CDecodeStream::CDecodeStream(CImageData *pid, IStream *pstrm)
  228. {
  229. CommonConstruct(pid);
  230. IUnknown_Set((IUnknown**)&_pstrmInner, pstrm);
  231. }
  232. CDecodeStream::CDecodeStream(CImageData *pid, LPCTSTR pszFilename, DWORD dwMode)
  233. {
  234. CommonConstruct(pid);
  235. lstrcpyn(_szFilename, pszFilename, ARRAYSIZE(_szFilename));
  236. // ignore the mode
  237. }
  238. //reload is only used for file streams
  239. void CDecodeStream::Reload()
  240. {
  241. if (IsFileStream())
  242. {
  243. ATOMICRELEASE(_pstrmInner);
  244. if (_fSuspendable)
  245. {
  246. ZeroMemory(&_liPos, sizeof(_liPos));
  247. }
  248. }
  249. }
  250. HRESULT CDecodeStream::Suspend()
  251. {
  252. HRESULT hr;
  253. if (IsFileStream() && _pstrmInner && _fSuspendable)
  254. {
  255. // Remember the file position so we can restore it when we resume
  256. const LARGE_INTEGER liZero = { 0, 0 };
  257. hr = _pstrmInner->Seek(liZero, FILE_CURRENT, (ULARGE_INTEGER*)&_liPos);
  258. if (SUCCEEDED(hr))
  259. {
  260. #ifdef DEBUG // Need #ifdef because we call a function
  261. TraceMsg(TF_SUSPENDRESUME, "ds.Suspend %s, pos=0x%08x",
  262. PathFindFileName(_szFilename), _liPos.LowPart);
  263. #endif
  264. ATOMICRELEASE(_pstrmInner);
  265. hr = S_OK;
  266. }
  267. }
  268. else
  269. {
  270. hr = S_FALSE; // Not suspendable or already suspended
  271. }
  272. return hr;
  273. }
  274. HRESULT CDecodeStream::Resume(BOOL fLoadFull)
  275. {
  276. HRESULT hr;
  277. if (_pstrmInner)
  278. {
  279. return S_OK;
  280. }
  281. if (fLoadFull)
  282. {
  283. _fSuspendable = FALSE;
  284. }
  285. if (IsFileStream())
  286. {
  287. if (PathIsURL(_szFilename))
  288. {
  289. // TODO: use URLMon to load the image, make sure we check for being allowed to go on-line
  290. hr = E_NOTIMPL;
  291. }
  292. else
  293. {
  294. if (!fLoadFull)
  295. {
  296. hr = SHCreateStreamOnFileEx(_szFilename, STGM_READ | STGM_SHARE_DENY_NONE, 0, FALSE, NULL, &_pstrmInner);
  297. if (SUCCEEDED(hr))
  298. {
  299. hr = _pstrmInner->Seek(_liPos, FILE_BEGIN, NULL);
  300. if (SUCCEEDED(hr))
  301. {
  302. #ifdef DEBUG // Need #ifdef because we call a function
  303. TraceMsg(TF_SUSPENDRESUME, "ds.Resumed %s, pos=0x%08x",
  304. PathFindFileName(_szFilename), _liPos.LowPart);
  305. #endif
  306. }
  307. else
  308. {
  309. ATOMICRELEASE(_pstrmInner);
  310. }
  311. }
  312. }
  313. else
  314. {
  315. hr = S_OK;
  316. HANDLE hFile = CreateFile(_szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
  317. if (INVALID_HANDLE_VALUE != hFile)
  318. {
  319. LARGE_INTEGER liSize = {0};
  320. // we can't handle huge files
  321. if (GetFileSizeEx(hFile, &liSize) && !liSize.HighPart)
  322. {
  323. DWORD dwToRead = liSize.LowPart;
  324. HGLOBAL hGlobal = GlobalAlloc(GHND, dwToRead);
  325. if (hGlobal)
  326. {
  327. void *pv = GlobalLock(hGlobal);
  328. DWORD dwRead;
  329. if (pv)
  330. {
  331. if (ReadFile(hFile, pv, dwToRead, &dwRead, NULL))
  332. {
  333. ASSERT(dwRead == dwToRead);
  334. GlobalUnlock(hGlobal);
  335. hr = CreateStreamOnHGlobal(hGlobal, TRUE, &_pstrmInner);
  336. }
  337. else
  338. {
  339. GlobalUnlock(hGlobal);
  340. }
  341. }
  342. if (!_pstrmInner)
  343. {
  344. GlobalFree(hGlobal);
  345. }
  346. }
  347. }
  348. CloseHandle(hFile);
  349. }
  350. }
  351. if (SUCCEEDED(hr) && !_pstrmInner)
  352. {
  353. DWORD dw = GetLastError();
  354. hr = HRESULT_FROM_WIN32(dw);
  355. }
  356. }
  357. if (FAILED(hr))
  358. {
  359. #ifdef DEBUG // Need #ifdef because we call a function
  360. TraceMsg(TF_SUSPENDRESUME, "ds.Resume %s failed: %08x",
  361. PathFindFileName(_szFilename), hr);
  362. #endif
  363. }
  364. }
  365. else
  366. {
  367. hr = E_FAIL; // Can't resume without a filename
  368. }
  369. return hr;
  370. }
  371. //
  372. // This function is called at the top of each IStream method to make
  373. // sure that the stream has not been cancelled and resumes it if
  374. // necessary.
  375. //
  376. HRESULT CDecodeStream::FilterAccess()
  377. {
  378. if (_pidOwner && _pidOwner->QueryAbort(_pidOwner))
  379. {
  380. return E_ABORT;
  381. }
  382. return Resume();
  383. }
  384. HRESULT CDecodeStream::DisplayName(LPWSTR wszName, UINT cch)
  385. {
  386. HRESULT hr = E_FAIL;
  387. if (IsFileStream())
  388. {
  389. // from the filename generate the leaf name which we can
  390. // return the name to caller.
  391. LPTSTR pszFilename = PathFindFileName(_szFilename);
  392. if (pszFilename)
  393. {
  394. SHTCharToUnicode(pszFilename, wszName, cch);
  395. hr = S_OK;
  396. }
  397. }
  398. else if (_pstrmInner)
  399. {
  400. // this is a stream, so lets get the display name from the that stream
  401. // and return that into the buffer that the caller has given us.
  402. STATSTG stat;
  403. hr = _pstrmInner->Stat(&stat, 0x0);
  404. if (SUCCEEDED(hr))
  405. {
  406. if (stat.pwcsName)
  407. {
  408. StrCpyN(wszName, stat.pwcsName, cch);
  409. CoTaskMemFree(stat.pwcsName);
  410. }
  411. else
  412. {
  413. hr = E_FAIL;
  414. }
  415. }
  416. }
  417. else
  418. {
  419. hr = E_FAIL;
  420. }
  421. return hr;
  422. }
  423. //
  424. // Now the boring part...
  425. //
  426. // *** IUnknown ***
  427. HRESULT CDecodeStream::QueryInterface(REFIID riid, LPVOID * ppvObj)
  428. {
  429. static const QITAB qit[] =
  430. {
  431. QITABENT(CDecodeStream, IStream),
  432. { 0 },
  433. };
  434. return QISearch(this, qit, riid, ppvObj);
  435. }
  436. ULONG CDecodeStream::AddRef()
  437. {
  438. return InterlockedIncrement(&_cRef);
  439. }
  440. ULONG CDecodeStream::Release()
  441. {
  442. if (InterlockedDecrement(&_cRef))
  443. {
  444. return _cRef;
  445. }
  446. delete this;
  447. return 0;
  448. }
  449. // *** IStream ***
  450. #define WRAP_METHOD(fn, args, argl) \
  451. HRESULT CDecodeStream::fn args \
  452. { \
  453. HRESULT hr = FilterAccess(); \
  454. if (SUCCEEDED(hr)) \
  455. { \
  456. hr = _pstrmInner->fn argl; \
  457. } \
  458. return hr; \
  459. }
  460. WRAP_METHOD(Read, (void *pv, ULONG cb, ULONG *pcbRead), (pv, cb, pcbRead))
  461. WRAP_METHOD(Write, (void const *pv, ULONG cb, ULONG *pcbWritten), (pv, cb, pcbWritten))
  462. WRAP_METHOD(Seek, (LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition),
  463. (dlibMove, dwOrigin, plibNewPosition))
  464. WRAP_METHOD(SetSize, (ULARGE_INTEGER libNewSize), (libNewSize))
  465. WRAP_METHOD(CopyTo, (IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten),
  466. (pstm, cb, pcbRead, pcbWritten))
  467. WRAP_METHOD(Commit, (DWORD grfCommitFlags), (grfCommitFlags))
  468. WRAP_METHOD(Revert, (), ())
  469. WRAP_METHOD(LockRegion, (ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType), (libOffset, cb, dwLockType))
  470. WRAP_METHOD(UnlockRegion, (ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType), (libOffset, cb, dwLockType))
  471. WRAP_METHOD(Stat, (STATSTG *pstatstg, DWORD grfStatFlag), (pstatstg, grfStatFlag))
  472. WRAP_METHOD(Clone, (IStream **ppstm), (ppstm))
  473. #undef WRAP_METHOD
  474. ////////////////////////////////////////////////////////////////////////////
  475. class CFmtEnum : public IEnumSTATPROPSETSTG, public NonATLObject
  476. {
  477. public:
  478. STDMETHODIMP Next(ULONG celt, STATPROPSETSTG *rgelt, ULONG *pceltFetched);
  479. STDMETHODIMP Skip(ULONG celt);
  480. STDMETHODIMP Reset(void);
  481. STDMETHODIMP Clone(IEnumSTATPROPSETSTG **ppenum);
  482. STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj);
  483. STDMETHODIMP_(ULONG) AddRef();
  484. STDMETHODIMP_(ULONG) Release();
  485. CFmtEnum(IEnumSTATPROPSETSTG *pEnum);
  486. private:
  487. ~CFmtEnum();
  488. IEnumSTATPROPSETSTG *_pEnum;
  489. ULONG _idx;
  490. LONG _cRef;
  491. };
  492. #define HR_FROM_STATUS(x) ((x) == Ok) ? S_OK : E_FAIL
  493. // IUnknown
  494. STDMETHODIMP CImageData::QueryInterface(REFIID riid, void **ppv)
  495. {
  496. static const QITAB qit[] =
  497. {
  498. QITABENT(CImageData, IShellImageData),
  499. QITABENT(CImageData, IPersistFile),
  500. QITABENT(CImageData, IPersistStream),
  501. QITABENT(CImageData, IPropertySetStorage),
  502. { 0 },
  503. };
  504. return QISearch(this, qit, riid, ppv);
  505. }
  506. STDMETHODIMP_(ULONG) CImageData::AddRef()
  507. {
  508. return InterlockedIncrement(&_cRef);
  509. }
  510. STDMETHODIMP_(ULONG) CImageData::Release()
  511. {
  512. if (InterlockedDecrement(&_cRef))
  513. return _cRef;
  514. delete this;
  515. return 0;
  516. }
  517. CImageData::CImageData(BOOL fPropertyOnly) : _cRef(1), _cImages(1), _fPropertyOnly(fPropertyOnly), _fPropertyChanged(FALSE)
  518. {
  519. // Catch unexpected STACK allocations which would break us.
  520. ASSERT(_dwMode == 0);
  521. ASSERT(_pstrm == NULL);
  522. ASSERT(_fLoaded == FALSE);
  523. ASSERT(_fDecoded == FALSE);
  524. ASSERT(_dwFlags == 0);
  525. ASSERT(_cxDesired == 0);
  526. ASSERT(_cyDesired == 0);
  527. ASSERT(_pImage == NULL);
  528. ASSERT(_pimgEdited == NULL);
  529. ASSERT(_hdpaProps == NULL);
  530. ASSERT(_dwRotation == 0);
  531. ASSERT(_fDestructive == FALSE);
  532. ASSERT(_fAnimated == FALSE);
  533. ASSERT(_fLoopForever == FALSE);
  534. ASSERT(_cLoop == 0);
  535. ASSERT(_fEditable == FALSE);
  536. ASSERT(_iCurrent == 0);
  537. ASSERT(_piAnimDelay == NULL);
  538. ASSERT(_ppbEncoderParams == NULL);
  539. ASSERT(_pAbort == NULL);
  540. }
  541. CImageData::~CImageData()
  542. {
  543. if (_fPropertyOnly && _fPropertyChanged)
  544. {
  545. Save((LPCTSTR)NULL, FALSE);
  546. }
  547. if (_pstrm)
  548. {
  549. _pstrm->Detach();
  550. _pstrm->Release();
  551. }
  552. if (_pImage)
  553. {
  554. delete _pImage; // discard the pImage object we have been using
  555. _pImage = NULL;
  556. }
  557. if (_pimgEdited)
  558. {
  559. delete _pimgEdited;
  560. _pimgEdited = NULL;
  561. }
  562. if (_piAnimDelay)
  563. LocalFree(_piAnimDelay); // do we have an array of image frame delays to destroy
  564. if (_hdpaProps)
  565. DPA_DestroyCallback(_hdpaProps, _FreeProps, NULL);
  566. if (_fLoaded)
  567. {
  568. _dsaChangedProps.Destroy();
  569. }
  570. ATOMICRELEASE(_pAbort);
  571. }
  572. // IPersistStream
  573. HRESULT CImageData::_SetDecodeStream(CDecodeStream *pds)
  574. {
  575. ASSERT(_pstrm == NULL);
  576. _pstrm = pds;
  577. if (_pstrm)
  578. {
  579. _fLoaded = TRUE;
  580. _dsaChangedProps.Create(10);
  581. return S_OK;
  582. }
  583. else
  584. {
  585. return E_OUTOFMEMORY;
  586. }
  587. }
  588. HRESULT CImageData::Load(IStream *pstrm)
  589. {
  590. if (_fLoaded)
  591. return STG_E_INUSE;
  592. return _SetDecodeStream(new CDecodeStream(this, pstrm));
  593. }
  594. HRESULT CImageData::Save(IStream *pstrm, BOOL fClearDirty)
  595. {
  596. HRESULT hr = _EnsureImage();
  597. if (SUCCEEDED(hr))
  598. {
  599. hr = _SaveImages(pstrm, &_guidFmt);
  600. }
  601. return hr;
  602. }
  603. // IPersistFile methods
  604. HRESULT CImageData::IsDirty()
  605. {
  606. return (_dwRotation || _pimgEdited) ? S_OK : S_FALSE;
  607. }
  608. HRESULT CImageData::Load(LPCOLESTR pszFileName, DWORD dwMode)
  609. {
  610. if (_fLoaded)
  611. return STG_E_INUSE;
  612. if (!*pszFileName)
  613. return E_INVALIDARG;
  614. return _SetDecodeStream(new CDecodeStream(this, pszFileName, dwMode));
  615. }
  616. #define ATTRIBUTES_TEMPFILE (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_TEMPORARY)
  617. // IPersistFile::Save() see SDK docs
  618. HRESULT CImageData::Save(LPCOLESTR pszFile, BOOL fRemember)
  619. {
  620. HRESULT hr = _EnsureImage();
  621. if (SUCCEEDED(hr))
  622. {
  623. // If this fires, then somebody managed to get _EnsureImage to
  624. // succeed without ever actually loading anything... (?)
  625. ASSERT(_pstrm);
  626. if (pszFile == NULL && !_pstrm->IsFileStream())
  627. {
  628. // Trying to "save with same name you loaded from"
  629. // when we weren't loaded from a file to begin with
  630. hr = HRESULT_FROM_WIN32(ERROR_INVALID_NAME);
  631. }
  632. else
  633. {
  634. // we default to saving in the original format that we were given
  635. // if the name is NULL (we will also attempt to replace the original file).
  636. TCHAR szTempFile[MAX_PATH];
  637. GUID guidFmt = _guidFmt; // default to original format
  638. BOOL fReplaceOriginal = _pstrm->IsFileStream() &&
  639. ((NULL == pszFile) || (S_OK == IsSameFile(pszFile, _pstrm->GetFilename())));
  640. if (fReplaceOriginal)
  641. {
  642. // we are being told to save to the current file, but we have the current file locked open.
  643. // To get around this we save to a temporary file, close our handles on the current file,
  644. // and then replace the current file with the new file
  645. hr = _MakeTempFile(szTempFile);
  646. pszFile = szTempFile;
  647. }
  648. else if (!_ppbEncoderParams)
  649. {
  650. // the caller did not tell us which encoder to use?
  651. // determine the encoder based on the target file name
  652. hr = _GetDataFormatFromPath(pszFile, &guidFmt);
  653. }
  654. if (SUCCEEDED(hr))
  655. {
  656. // the attributes are important as they need to match those of the
  657. // temp file we created else this call fails
  658. IStream *pstrm;
  659. hr = SHCreateStreamOnFileEx(pszFile, STGM_WRITE | STGM_CREATE,
  660. fReplaceOriginal ? ATTRIBUTES_TEMPFILE : 0, TRUE, NULL, &pstrm);
  661. if (SUCCEEDED(hr))
  662. {
  663. hr = _SaveImages(pstrm, &guidFmt);
  664. pstrm->Release();
  665. if (SUCCEEDED(hr) && fReplaceOriginal)
  666. {
  667. hr = _ReplaceFile(szTempFile);
  668. if (SUCCEEDED(hr))
  669. {
  670. delete _pImage;
  671. _pImage = NULL;
  672. _fDecoded = FALSE;
  673. _pstrm->Reload();
  674. DWORD iCurrentPage = _iCurrent;
  675. hr = Decode(_dwFlags, _cxDesired, _cyDesired);
  676. if (iCurrentPage < _cImages)
  677. _iCurrent = iCurrentPage;
  678. }
  679. }
  680. }
  681. if (FAILED(hr) && fReplaceOriginal)
  682. {
  683. // make sure temp file is gone
  684. DeleteFile(szTempFile);
  685. }
  686. }
  687. }
  688. }
  689. return hr;
  690. }
  691. HRESULT CImageData::SaveCompleted(LPCOLESTR pszFileName)
  692. {
  693. return E_NOTIMPL;
  694. }
  695. HRESULT CImageData::GetCurFile(LPOLESTR *ppszFileName)
  696. {
  697. if (_pstrm && _pstrm->IsFileStream())
  698. return SHStrDup(_pstrm->GetFilename(), ppszFileName);
  699. return E_FAIL;
  700. }
  701. // handle decoding the image this includes updating our cache of the images
  702. HRESULT CImageData::_EnsureImage()
  703. {
  704. if (_fDecoded && _pImage)
  705. return S_OK;
  706. return E_FAIL;
  707. }
  708. HRESULT CImageData::_SuspendStream()
  709. {
  710. HRESULT hr = S_OK;
  711. if (_pstrm)
  712. {
  713. hr = _pstrm->Suspend();
  714. }
  715. return hr;
  716. }
  717. HRESULT CImageData::_GetDisplayedImage()
  718. {
  719. HRESULT hr = _EnsureImage();
  720. if (SUCCEEDED(hr))
  721. {
  722. const CLSID * pclsidFrameDim = _fAnimated ? &FrameDimensionTime : &FrameDimensionPage;
  723. hr = HR_FROM_STATUS(_pImage->SelectActiveFrame(pclsidFrameDim, _iCurrent));
  724. }
  725. return hr;
  726. }
  727. // IShellImageData method
  728. HRESULT CImageData::Decode(DWORD dwFlags, ULONG cx, ULONG cy)
  729. {
  730. if (!_fLoaded)
  731. return E_FAIL;
  732. if (_fDecoded)
  733. return S_FALSE;
  734. HRESULT hr = S_OK;
  735. _dwFlags = dwFlags;
  736. _cxDesired = cx;
  737. _cyDesired = cy;
  738. //
  739. // Resume the stream now so GDI+ won't go nuts trying to detect the
  740. // image type of a file that it can't read...
  741. //
  742. hr = _pstrm->Resume(dwFlags & SHIMGDEC_LOADFULL);
  743. // if that succeeded then we can create an image using the stream and decode
  744. // the images from that. once we are done we will release the objects.
  745. if (SUCCEEDED(hr))
  746. {
  747. _pImage = new Image(_pstrm, TRUE);
  748. if (_pImage)
  749. {
  750. if (Ok != _pImage->GetLastStatus())
  751. {
  752. delete _pImage;
  753. _pImage = NULL;
  754. hr = E_FAIL;
  755. }
  756. else
  757. {
  758. _fEditable = TRUE;
  759. if (_dwFlags & SHIMGDEC_THUMBNAIL)
  760. {
  761. // for thumbnails, _cxDesired and _cyDesired define a bounding rectangle but we
  762. // should maintain the original aspect ratio.
  763. int cxT = _pImage->GetWidth();
  764. int cyT = _pImage->GetHeight();
  765. if (cxT > _cxDesired || cyT > _cyDesired)
  766. {
  767. if (Int32x32To64(_cxDesired, cyT) > Int32x32To64(cxT, _cyDesired))
  768. {
  769. // constrained by height
  770. cxT = MulDiv(cxT, _cyDesired, cyT);
  771. if (cxT < 1) cxT = 1;
  772. cyT = _cyDesired;
  773. }
  774. else
  775. {
  776. // constrained by width
  777. cyT = MulDiv(cyT, _cxDesired, cxT);
  778. if (cyT < 1) cyT = 1;
  779. cxT = _cxDesired;
  780. }
  781. }
  782. Image * pThumbnail;
  783. pThumbnail = _pImage->GetThumbnailImage(cxT, cyT, QueryAbort, this);
  784. //
  785. // GDI+ sometimes forgets to tell us it gave up due to an abort.
  786. //
  787. if (pThumbnail && !QueryAbort(this))
  788. {
  789. delete _pImage;
  790. _pImage = pThumbnail;
  791. }
  792. else
  793. {
  794. delete pThumbnail; // "delete" ignores NULL pointers
  795. hr = E_FAIL;
  796. }
  797. }
  798. else
  799. {
  800. _pImage->GetRawFormat(&_guidFmt); // read the raw format of the file
  801. if (_guidFmt == ImageFormatTIFF)
  802. {
  803. VARIANT var;
  804. if (SUCCEEDED(_GetProperty(PropertyTagExifIFD, &var, VT_UI4)))
  805. {
  806. // TIFF images with an EXIF IFD aren't editable by GDI+
  807. _fEditable = FALSE;
  808. VariantClear(&var);
  809. }
  810. }
  811. // is this an animated/multi page image?
  812. _cImages = _pImage->GetFrameCount(&FrameDimensionPage);
  813. if (_cImages <= 1)
  814. {
  815. _cImages = _pImage->GetFrameCount(&FrameDimensionTime);
  816. if (_cImages > 1)
  817. {
  818. _fAnimated = TRUE;
  819. // store the frame delays in PropertyItem *_piAnimDelay;
  820. UINT cb = _pImage->GetPropertyItemSize(PropertyTagFrameDelay);
  821. if (cb)
  822. {
  823. _piAnimDelay = (PropertyItem*)LocalAlloc(LPTR, cb);
  824. if (_piAnimDelay)
  825. {
  826. if (Ok != _pImage->GetPropertyItem(PropertyTagFrameDelay, cb, _piAnimDelay))
  827. {
  828. LocalFree(_piAnimDelay);
  829. _piAnimDelay = NULL;
  830. }
  831. }
  832. }
  833. }
  834. }
  835. _pImage->GetLastStatus(); // 145081: clear the error from the first call to _pImage->GetFrameCount so that
  836. // the later call to _GetProperty won't immediately fail.
  837. // we wouldn't have to do this always if the gdi interface didn't maintain its own
  838. // error code and fail automatically based on it without allowing us to check it
  839. // without resetting it.
  840. // some decoders will return zero as the frame count when they don't support that dimension
  841. if (0 == _cImages)
  842. _cImages = 1;
  843. // is it a looping image? this will only be if its animated
  844. if (_fAnimated)
  845. {
  846. VARIANT var;
  847. if (SUCCEEDED(_GetProperty(PropertyTagLoopCount, &var, VT_UI4)))
  848. {
  849. _cLoop = var.ulVal;
  850. _fLoopForever = (_cLoop == 0);
  851. VariantClear(&var);
  852. }
  853. }
  854. PixelFormat pf = _pImage->GetPixelFormat();
  855. // can we edit this image? NOTE: The caller needs to ensure that the file is writeable
  856. // all of that jazz, we only check if we have an encoder for this format. Just cause we
  857. // can edit a file doesn't mean the file can be written to the original source location.
  858. // We can't edit images with > 8 bits per channel either
  859. if (_fEditable)
  860. {
  861. _fEditable = !_fAnimated && SUCCEEDED(_GetEncoderFromFormat(&_guidFmt, NULL)) && !IsExtendedPixelFormat(pf);
  862. }
  863. }
  864. }
  865. }
  866. else
  867. {
  868. hr = E_OUTOFMEMORY; // failed to allocate the image decoder
  869. }
  870. }
  871. // Suspend the stream so we don't leave the file open
  872. _SuspendStream();
  873. _fDecoded = TRUE;
  874. return hr;
  875. }
  876. HRESULT CImageData::Draw(HDC hdc, LPRECT prcDest, LPRECT prcSrc)
  877. {
  878. if (!prcDest)
  879. return E_INVALIDARG; // not much chance without a destination to paint into
  880. HRESULT hr = _EnsureImage();
  881. if (SUCCEEDED(hr))
  882. {
  883. Image *pimg = _pimgEdited ? _pimgEdited : _pImage;
  884. RECT rcSrc;
  885. if (prcSrc)
  886. {
  887. rcSrc.left = prcSrc->left;
  888. rcSrc.top = prcSrc->top;
  889. rcSrc.right = RECTWIDTH(*prcSrc);
  890. rcSrc.bottom= RECTHEIGHT(*prcSrc);
  891. }
  892. else
  893. {
  894. rcSrc.left = 0;
  895. rcSrc.top = 0;
  896. rcSrc.right = pimg->GetWidth();
  897. rcSrc.bottom= pimg->GetHeight();
  898. }
  899. Unit unit;
  900. RectF rectf;
  901. if (Ok==pimg->GetBounds(&rectf, &unit) && UnitPixel==unit)
  902. {
  903. rcSrc.left += (int)rectf.X;
  904. rcSrc.top += (int)rectf.Y;
  905. }
  906. // we have a source rectangle so lets apply that when we render this image.
  907. Rect rc(prcDest->left, prcDest->top, RECTWIDTH(*prcDest), RECTHEIGHT(*prcDest));
  908. DWORD dwLayout = SetLayout(hdc, LAYOUT_BITMAPORIENTATIONPRESERVED);
  909. Graphics g(hdc);
  910. g.SetPageUnit(UnitPixel); // WARNING: If you remove this line (as has happened twice since Beta 1) you will break printing.
  911. if (_guidFmt == ImageFormatTIFF)
  912. {
  913. g.SetInterpolationMode(InterpolationModeHighQualityBilinear);
  914. }
  915. hr = HR_FROM_STATUS(g.DrawImage(pimg,
  916. rc,
  917. rcSrc.left, rcSrc.top,
  918. rcSrc.right, rcSrc.bottom,
  919. UnitPixel, NULL, QueryAbort, this));
  920. //
  921. // GDI+ sometimes forgets to tell us it gave up due to an abort.
  922. //
  923. if (SUCCEEDED(hr) && QueryAbort(this))
  924. hr = E_ABORT;
  925. if (GDI_ERROR != dwLayout)
  926. SetLayout(hdc, dwLayout);
  927. }
  928. // Suspend the stream so we don't leave the file open
  929. _SuspendStream();
  930. return hr;
  931. }
  932. HRESULT CImageData::SelectPage(ULONG iPage)
  933. {
  934. if (iPage >= _cImages)
  935. return OLE_E_ENUM_NOMORE;
  936. if (_iCurrent != iPage)
  937. {
  938. // Since we are moving to a different page throw away any edits
  939. DiscardEdit();
  940. }
  941. _iCurrent = iPage;
  942. return _GetDisplayedImage();
  943. }
  944. HRESULT CImageData::NextFrame()
  945. {
  946. if (!_fAnimated)
  947. return S_FALSE; // not animated, so no next frame
  948. // if this is the last image, then lets look at the loop
  949. // counter and try and decide if we should cycle this image
  950. // around or not.
  951. if ((_iCurrent == _cImages-1) && !_fLoopForever)
  952. {
  953. if (_cLoop)
  954. _cLoop --;
  955. // if cLoop is zero then we're done looping
  956. if (_cLoop == 0)
  957. return S_FALSE;
  958. }
  959. // advance to the next image in the sequence
  960. _iCurrent = (_iCurrent+1) % _cImages;
  961. return _GetDisplayedImage();
  962. }
  963. HRESULT CImageData::NextPage()
  964. {
  965. if (_iCurrent >= _cImages-1)
  966. return OLE_E_ENUM_NOMORE;
  967. // Since we are moving to the next page throw away any edits
  968. DiscardEdit();
  969. _iCurrent++;
  970. return _GetDisplayedImage();
  971. }
  972. HRESULT CImageData::PrevPage()
  973. {
  974. if (_iCurrent == 0)
  975. return OLE_E_ENUM_NOMORE;
  976. // Since we are moving to the next page throw away any edits
  977. DiscardEdit();
  978. _iCurrent--;
  979. return _GetDisplayedImage();
  980. }
  981. STDMETHODIMP CImageData::IsTransparent()
  982. {
  983. HRESULT hr = _EnsureImage();
  984. if (SUCCEEDED(hr))
  985. hr = (_pImage->GetFlags() & ImageFlagsHasAlpha) ? S_OK : S_FALSE;
  986. return hr;
  987. }
  988. HRESULT CImageData::IsVector()
  989. {
  990. HRESULT hr = _EnsureImage();
  991. if (SUCCEEDED(hr))
  992. {
  993. hr = (_pImage->GetFlags() & ImageFlagsScalable) ? S_OK : S_FALSE;
  994. }
  995. return hr;
  996. }
  997. HRESULT CImageData::GetSize(SIZE *pSize)
  998. {
  999. HRESULT hr = _EnsureImage();
  1000. if (SUCCEEDED(hr))
  1001. {
  1002. Image *pimg = _pimgEdited ? _pimgEdited : _pImage;
  1003. pSize->cx = pimg->GetWidth();
  1004. pSize->cy = pimg->GetHeight();
  1005. }
  1006. return hr;
  1007. }
  1008. HRESULT CImageData::GetRawDataFormat(GUID *pfmt)
  1009. {
  1010. HRESULT hr = _EnsureImage();
  1011. if (SUCCEEDED(hr))
  1012. {
  1013. *pfmt = _guidFmt;
  1014. }
  1015. return hr;
  1016. }
  1017. HRESULT CImageData::GetPixelFormat(PixelFormat *pfmt)
  1018. {
  1019. HRESULT hr = _EnsureImage();
  1020. if (SUCCEEDED(hr))
  1021. {
  1022. *pfmt = _pImage->GetPixelFormat();
  1023. }
  1024. return hr;
  1025. }
  1026. HRESULT CImageData::GetDelay(DWORD *pdwDelay)
  1027. {
  1028. HRESULT hr = _EnsureImage();
  1029. DWORD dwFrame = _iCurrent;
  1030. if (SUCCEEDED(hr))
  1031. {
  1032. hr = E_FAIL;
  1033. if (_piAnimDelay)
  1034. {
  1035. if (_piAnimDelay->length != (sizeof(DWORD) * _cImages))
  1036. {
  1037. dwFrame = 0; // if array is not the expected size, be safe and just grab the delay of the first image
  1038. }
  1039. CopyMemory(pdwDelay, (void *)((UINT_PTR)_piAnimDelay->value + dwFrame * sizeof(DWORD)), sizeof(DWORD));
  1040. *pdwDelay = *pdwDelay * 10;
  1041. if (*pdwDelay < 100)
  1042. {
  1043. *pdwDelay = 100; // hack: do the same thing as mshtml, see inetcore\mshtml\src\site\download\imggif.cxx!CImgTaskGif::ReadGIFMaster
  1044. }
  1045. hr = S_OK;
  1046. }
  1047. }
  1048. return hr;
  1049. }
  1050. HRESULT CImageData::IsDecoded()
  1051. {
  1052. return _pImage ? S_OK : S_FALSE;
  1053. }
  1054. HRESULT CImageData::DisplayName(LPWSTR wszName, UINT cch)
  1055. {
  1056. HRESULT hr = E_FAIL;
  1057. // always set the out parameter to something known
  1058. *wszName = L'\0';
  1059. if (_pstrm)
  1060. {
  1061. hr = _pstrm->DisplayName(wszName, cch);
  1062. }
  1063. // REVIEW: If the user has selected not to view file extentions for known types should we hide the extention?
  1064. return hr;
  1065. }
  1066. // property handling code - decoding, conversion and other packing
  1067. HRESULT CImageData::_PropImgToVariant(PropertyItem *pi, VARIANT *pvar)
  1068. {
  1069. HRESULT hr = S_OK;
  1070. switch (pi->type)
  1071. {
  1072. case PropertyTagTypeByte:
  1073. pvar->vt = VT_UI1;
  1074. // check for multi-valued property and convert to safearray if found
  1075. if (pi->length > sizeof(UCHAR))
  1076. {
  1077. SAFEARRAYBOUND bound;
  1078. bound.cElements = pi->length/sizeof(UCHAR);
  1079. bound.lLbound = 0;
  1080. pvar->vt |= VT_ARRAY;
  1081. hr = E_OUTOFMEMORY;
  1082. pvar->parray = SafeArrayCreate(VT_UI1, 1, &bound);
  1083. if (pvar->parray)
  1084. {
  1085. void *pv;
  1086. hr = SafeArrayAccessData (pvar->parray, &pv);
  1087. if (SUCCEEDED(hr))
  1088. {
  1089. CopyMemory(pv, pi->value, pi->length);
  1090. SafeArrayUnaccessData(pvar->parray);
  1091. }
  1092. }
  1093. }
  1094. else
  1095. {
  1096. pvar->bVal = *((UCHAR*)pi->value);
  1097. }
  1098. break;
  1099. case PropertyTagTypeShort:
  1100. pvar->vt = VT_UI2;
  1101. pvar->uiVal = *((USHORT*)pi->value);
  1102. break;
  1103. case PropertyTagTypeLong:
  1104. pvar->vt = VT_UI4;
  1105. pvar->ulVal = *((ULONG*)pi->value);
  1106. break;
  1107. case PropertyTagTypeASCII:
  1108. {
  1109. WCHAR szValue[MAX_PATH];
  1110. SHAnsiToUnicode(((LPSTR)pi->value), szValue, ARRAYSIZE(szValue));
  1111. hr = InitVariantFromStr(pvar, szValue);
  1112. }
  1113. break;
  1114. case PropertyTagTypeRational:
  1115. {
  1116. LONG *pl = (LONG*)pi->value;
  1117. LONG num = pl[0];
  1118. LONG den = pl[1];
  1119. pvar->vt = VT_R8;
  1120. if (0 == den)
  1121. pvar->dblVal = 0; // don't divide by zero
  1122. else
  1123. pvar->dblVal = ((double)num)/((double)den);
  1124. }
  1125. break;
  1126. case PropertyTagTypeUndefined:
  1127. case PropertyTagTypeSLONG:
  1128. case PropertyTagTypeSRational:
  1129. default:
  1130. hr = E_UNEXPECTED;
  1131. break;
  1132. }
  1133. return hr;
  1134. }
  1135. HRESULT CImageData::_GetProperty(PROPID id, VARIANT *pvar, VARTYPE vt)
  1136. {
  1137. UINT cb = _pImage->GetPropertyItemSize(id);
  1138. HRESULT hr = HR_FROM_STATUS(_pImage->GetLastStatus());
  1139. if (cb && SUCCEEDED(hr))
  1140. {
  1141. PropertyItem *pi = (PropertyItem*)LocalAlloc(LPTR, cb);
  1142. if (pi)
  1143. {
  1144. hr = HR_FROM_STATUS(_pImage->GetPropertyItem(id, cb, pi));
  1145. if (SUCCEEDED(hr))
  1146. {
  1147. hr = _PropImgToVariant(pi, pvar);
  1148. }
  1149. LocalFree(pi);
  1150. }
  1151. }
  1152. if (SUCCEEDED(hr) && (vt != 0) && (pvar->vt != vt))
  1153. hr = VariantChangeType(pvar, pvar, 0, vt);
  1154. return hr;
  1155. }
  1156. HRESULT CImageData::GetProperties(DWORD dwMode, IPropertySetStorage **ppss)
  1157. {
  1158. HRESULT hr = _EnsureProperties(NULL);
  1159. if (SUCCEEDED(hr))
  1160. {
  1161. hr = QueryInterface(IID_PPV_ARG(IPropertySetStorage, ppss));
  1162. }
  1163. return hr;
  1164. }
  1165. HRESULT CImageData::_CreateMemPropSetStorage(IPropertySetStorage **ppss)
  1166. {
  1167. *ppss = NULL;
  1168. ILockBytes *plb;
  1169. HRESULT hr = CreateILockBytesOnHGlobal(NULL, TRUE, &plb);
  1170. if (SUCCEEDED(hr))
  1171. {
  1172. IStorage *pstg;
  1173. hr = StgCreateDocfileOnILockBytes(plb,
  1174. STGM_DIRECT|STGM_READWRITE|STGM_CREATE|STGM_SHARE_EXCLUSIVE, 0, &pstg);
  1175. if (SUCCEEDED(hr))
  1176. {
  1177. hr = pstg->QueryInterface(IID_PPV_ARG(IPropertySetStorage, ppss));
  1178. pstg->Release();
  1179. }
  1180. plb->Release();
  1181. }
  1182. return hr;
  1183. }
  1184. // _EnsureProperties returns an in-memory IPropertySetStorage for the current active frame
  1185. // For read-only files we may not be able to modify the property set, so handle access issues
  1186. // gracefully.
  1187. HRESULT CImageData::_EnsureProperties(IPropertySetStorage **ppss)
  1188. {
  1189. if (ppss)
  1190. {
  1191. *ppss = NULL;
  1192. }
  1193. Decode(SHIMGDEC_DEFAULT, 0, 0);
  1194. HRESULT hr = _EnsureImage();
  1195. if (SUCCEEDED(hr) && !_hdpaProps)
  1196. {
  1197. _hdpaProps = DPA_Create(_cImages);
  1198. if (!_hdpaProps)
  1199. {
  1200. hr = E_OUTOFMEMORY;
  1201. }
  1202. }
  1203. if (SUCCEEDED(hr))
  1204. {
  1205. IPropertySetStorage *pss = (IPropertySetStorage*)DPA_GetPtr(_hdpaProps, _iCurrent);
  1206. if (!pss)
  1207. {
  1208. hr = _CreateMemPropSetStorage(&pss);
  1209. if (SUCCEEDED(hr))
  1210. {
  1211. // fill in the NTFS or memory-based FMTID_ImageProperties if it doesn't already exist
  1212. IPropertyStorage *pps;
  1213. // we use CImagePropset to fill in the propertystorage when it is first created
  1214. if (SUCCEEDED(pss->Create(FMTID_ImageProperties, &CLSID_NULL, PROPSETFLAG_DEFAULT, STGM_READWRITE|STGM_SHARE_EXCLUSIVE, &pps)))
  1215. {
  1216. CImagePropSet *ppsImg = new CImagePropSet(_pImage, NULL, pps, FMTID_ImageProperties);
  1217. if (ppsImg)
  1218. {
  1219. ppsImg->SyncImagePropsToStorage();
  1220. ppsImg->Release();
  1221. }
  1222. pps->Release();
  1223. }
  1224. if (_guidFmt == ImageFormatJPEG || _guidFmt == ImageFormatTIFF)
  1225. {
  1226. // for now ignore failures here it's not a catastrophic problem if they aren't written
  1227. if (SUCCEEDED(pss->Create(FMTID_SummaryInformation, &CLSID_NULL, PROPSETFLAG_DEFAULT, STGM_FAILIFTHERE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, &pps)))
  1228. {
  1229. CImagePropSet *ppsSummary = new CImagePropSet(_pImage, NULL, pps, FMTID_SummaryInformation);
  1230. if (ppsSummary)
  1231. {
  1232. ppsSummary->SyncImagePropsToStorage();
  1233. ppsSummary->Release();
  1234. }
  1235. pps->Release();
  1236. }
  1237. }
  1238. DPA_SetPtr(_hdpaProps, _iCurrent, pss);
  1239. }
  1240. }
  1241. if (SUCCEEDED(hr) && ppss)
  1242. {
  1243. *ppss = pss;
  1244. }
  1245. }
  1246. return hr;
  1247. }
  1248. // NOTE: ppps is an IN-OUT parameter
  1249. HRESULT CImageData::_CreatePropStorage(IPropertyStorage **ppps, REFFMTID fmtid)
  1250. {
  1251. HRESULT hr = E_FAIL;
  1252. if (_pImage)
  1253. {
  1254. CImagePropSet *ppsImg = new CImagePropSet(_pImage, this, *ppps, fmtid, _PropertyChanged);
  1255. ATOMICRELEASE(*ppps);
  1256. if (ppsImg)
  1257. {
  1258. hr = ppsImg->QueryInterface(IID_PPV_ARG(IPropertyStorage, ppps));
  1259. ppsImg->Release();
  1260. }
  1261. else
  1262. {
  1263. hr = E_OUTOFMEMORY;
  1264. }
  1265. }
  1266. return hr;
  1267. }
  1268. // IPropertySetStorage
  1269. //
  1270. // If the caller wants FMTID_ImageProperties use CImagePropSet
  1271. //
  1272. STDMETHODIMP CImageData::Create(REFFMTID fmtid, const CLSID *pclsid, DWORD grfFlags,
  1273. DWORD grfMode, IPropertyStorage **pppropstg)
  1274. {
  1275. *pppropstg = NULL;
  1276. IPropertySetStorage *pss;
  1277. HRESULT hr = _EnsureProperties(&pss);
  1278. if (SUCCEEDED(hr))
  1279. {
  1280. if ((S_OK != IsEditable()) && (grfMode & (STGM_READWRITE | STGM_WRITE)))
  1281. {
  1282. hr = STG_E_ACCESSDENIED;
  1283. }
  1284. }
  1285. if (SUCCEEDED(hr))
  1286. {
  1287. IPropertyStorage *pps = NULL;
  1288. hr = pss->Create(fmtid, pclsid, grfFlags, grfMode, &pps);
  1289. if (SUCCEEDED(hr))
  1290. {
  1291. hr = _CreatePropStorage(&pps, fmtid);
  1292. }
  1293. *pppropstg = pps;
  1294. }
  1295. return hr;
  1296. }
  1297. STDMETHODIMP CImageData::Open(REFFMTID fmtid, DWORD grfMode, IPropertyStorage **pppropstg)
  1298. {
  1299. *pppropstg = NULL;
  1300. IPropertySetStorage *pss;
  1301. HRESULT hr = _EnsureProperties(&pss);
  1302. if (SUCCEEDED(hr))
  1303. {
  1304. if ((S_OK != IsEditable()) && (grfMode & (STGM_READWRITE | STGM_WRITE)))
  1305. {
  1306. hr = STG_E_ACCESSDENIED;
  1307. }
  1308. }
  1309. if (SUCCEEDED(hr))
  1310. {
  1311. IPropertyStorage *pps = NULL;
  1312. // special case FMTID_ImageSummaryInformation...it is readonly and not backed up
  1313. // by a real property stream.
  1314. if (FMTID_ImageSummaryInformation != fmtid)
  1315. {
  1316. hr = pss->Open(fmtid, grfMode, &pps);
  1317. }
  1318. if (SUCCEEDED(hr))
  1319. {
  1320. hr = _CreatePropStorage(&pps, fmtid);
  1321. }
  1322. *pppropstg = pps;
  1323. }
  1324. return hr;
  1325. }
  1326. STDMETHODIMP CImageData::Delete(REFFMTID fmtid)
  1327. {
  1328. IPropertySetStorage *pss;
  1329. HRESULT hr = _EnsureProperties(&pss);
  1330. if (SUCCEEDED(hr))
  1331. {
  1332. hr = pss->Delete(fmtid);
  1333. }
  1334. return hr;
  1335. }
  1336. STDMETHODIMP CImageData::Enum(IEnumSTATPROPSETSTG **ppenum)
  1337. {
  1338. IPropertySetStorage *pss;
  1339. HRESULT hr = E_INVALIDARG;
  1340. if (ppenum)
  1341. {
  1342. hr = _EnsureProperties(&pss);
  1343. *ppenum = NULL;
  1344. }
  1345. if (SUCCEEDED(hr))
  1346. {
  1347. IEnumSTATPROPSETSTG *pEnum;
  1348. hr = pss->Enum(&pEnum);
  1349. if (SUCCEEDED(hr))
  1350. {
  1351. CFmtEnum *pFmtEnum = new CFmtEnum(pEnum);
  1352. if (pFmtEnum)
  1353. {
  1354. hr = pFmtEnum->QueryInterface(IID_PPV_ARG(IEnumSTATPROPSETSTG, ppenum));
  1355. pEnum->Release();
  1356. pFmtEnum->Release();
  1357. }
  1358. else
  1359. {
  1360. *ppenum = pEnum;
  1361. }
  1362. }
  1363. }
  1364. return hr;
  1365. }
  1366. // editing support
  1367. void CImageData::_SetEditImage(Image *pimgEdit)
  1368. {
  1369. if (_pimgEdited)
  1370. delete _pimgEdited;
  1371. _pimgEdited = pimgEdit;
  1372. }
  1373. // valid input is 0, 90, 180, or 270
  1374. HRESULT CImageData::Rotate(DWORD dwAngle)
  1375. {
  1376. HRESULT hr = _EnsureImage();
  1377. if (SUCCEEDED(hr))
  1378. {
  1379. // this has bad effects on animated images so don't do it
  1380. if (_fAnimated)
  1381. return E_NOTVALIDFORANIMATEDIMAGE;
  1382. RotateFlipType rft;
  1383. switch (dwAngle)
  1384. {
  1385. case 0:
  1386. hr = S_FALSE;
  1387. break;
  1388. case 90:
  1389. rft = Rotate90FlipNone;
  1390. break;
  1391. case 180:
  1392. rft = Rotate180FlipNone;
  1393. break;
  1394. case 270:
  1395. rft = Rotate270FlipNone;
  1396. break;
  1397. default:
  1398. hr = E_INVALIDARG;
  1399. }
  1400. if (S_OK == hr)
  1401. {
  1402. // get the current image we have displayed, ready to edit it.
  1403. Image * pimg = _pimgEdited ? _pimgEdited->Clone() : _pImage->Clone();
  1404. if (pimg)
  1405. {
  1406. // In order to fix Windows bug #325413 GDIPlus needs to throw away any decoded frames
  1407. // in memory for the cloned image. Therefore, we can no longer rely on
  1408. // RotateFlip to flip the decoded frame already in memory and must explicitly
  1409. // select it into the cloned image before calling RotateFlip to fix Windows Bug #368498
  1410. const CLSID * pclsidFrameDim = _fAnimated ? &FrameDimensionTime : &FrameDimensionPage;
  1411. hr = HR_FROM_STATUS(pimg->SelectActiveFrame(pclsidFrameDim, _iCurrent));
  1412. if (SUCCEEDED(hr))
  1413. {
  1414. hr = HR_FROM_STATUS(pimg->RotateFlip(rft));
  1415. if (SUCCEEDED(hr))
  1416. {
  1417. _dwRotation = (_dwRotation + dwAngle) % 360;
  1418. _SetEditImage(pimg);
  1419. }
  1420. }
  1421. if (FAILED(hr))
  1422. {
  1423. delete pimg;
  1424. }
  1425. }
  1426. else
  1427. {
  1428. hr = E_OUTOFMEMORY;
  1429. }
  1430. }
  1431. }
  1432. return hr;
  1433. }
  1434. HRESULT CImageData::Scale(ULONG cx, ULONG cy, InterpolationMode hints)
  1435. {
  1436. HRESULT hr = _EnsureImage();
  1437. if (SUCCEEDED(hr))
  1438. {
  1439. // this has bad effects on animated images
  1440. if (_fAnimated)
  1441. return E_NOTVALIDFORANIMATEDIMAGE;
  1442. Image * pimg = _pimgEdited ? _pimgEdited : _pImage;
  1443. // we have an image, lets determine the new size (preserving aspect ratio
  1444. // and ensuring that we don't end up with a 0 sized image as a result.
  1445. if (cy == 0)
  1446. cy = MulDiv(pimg->GetHeight(), cx, pimg->GetWidth());
  1447. else if (cx == 0)
  1448. cx = MulDiv(pimg->GetWidth(), cy, pimg->GetHeight());
  1449. cx = max(cx, 1);
  1450. cy = max(cy, 1);
  1451. // construct our new image and draw into it.
  1452. Bitmap *pimgNew = new Bitmap(cx, cy);
  1453. if (pimgNew)
  1454. {
  1455. Graphics g(pimgNew);
  1456. g.SetInterpolationMode(hints);
  1457. hr = HR_FROM_STATUS(g.DrawImage(pimg, Rect(0, 0, cx, cy),
  1458. 0, 0, pimg->GetWidth(), pimg->GetHeight(),
  1459. UnitPixel, NULL, QueryAbort, this));
  1460. //
  1461. // GDI+ sometimes forgets to tell us it gave up due to an abort.
  1462. //
  1463. if (SUCCEEDED(hr) && QueryAbort(this))
  1464. hr = E_ABORT;
  1465. if (SUCCEEDED(hr))
  1466. {
  1467. pimgNew->SetResolution(pimg->GetHorizontalResolution(), pimg->GetVerticalResolution());
  1468. _SetEditImage(pimgNew);
  1469. _fDestructive = TRUE; // the edit was Destructive
  1470. }
  1471. else
  1472. {
  1473. delete pimgNew;
  1474. }
  1475. }
  1476. else
  1477. {
  1478. hr = E_OUTOFMEMORY;
  1479. }
  1480. }
  1481. // Suspend the stream so we don't leave the file open
  1482. _SuspendStream();
  1483. return hr;
  1484. }
  1485. HRESULT CImageData::DiscardEdit()
  1486. {
  1487. // NB: The following code is not valid in all cases. For example, if you rotated, then scaled, then rotated again
  1488. // this code wouldn't work. We currently don't allow that scenario, so we shouldn't hit this problem, but it
  1489. // could be an issue for others using this object so we should figure out what to do about it. This code works
  1490. // if: 1.) your first edit is a scale, or 2.) you only do rotates. Also note, this code will clear the "dirty" bit
  1491. // so it would prevent the image from being saved, thus the failure of this code won't effect the data on disk.
  1492. if (_pimgEdited)
  1493. {
  1494. delete _pimgEdited;
  1495. _pimgEdited = NULL;
  1496. }
  1497. _dwRotation = 0;
  1498. _fDestructive = FALSE;
  1499. return S_OK;
  1500. }
  1501. // handle persisting images
  1502. HRESULT CImageData::SetEncoderParams(IPropertyBag *ppbEnc)
  1503. {
  1504. IUnknown_Set((IUnknown**)&_ppbEncoderParams, ppbEnc);
  1505. return S_OK;
  1506. }
  1507. // save images to the given stream that we have, using the format ID we have
  1508. void CImageData::_AddEncParameter(EncoderParameters *pep, GUID guidProperty, ULONG type, void *pv)
  1509. {
  1510. pep->Parameter[pep->Count].Guid = guidProperty;
  1511. pep->Parameter[pep->Count].Type = type;
  1512. pep->Parameter[pep->Count].NumberOfValues = 1;
  1513. pep->Parameter[pep->Count].Value = pv;
  1514. pep->Count++;
  1515. }
  1516. #define MAX_ENC_PARAMS 3
  1517. HRESULT CImageData::_SaveImages(IStream *pstrm, GUID * pguidFmt)
  1518. {
  1519. HRESULT hr = S_OK;
  1520. int iQuality = 0; // == 0 is a special case
  1521. // did the encoder specify a format for us to save in?
  1522. ASSERTMSG(NULL != pguidFmt, "Invalid pguidFmt passed to internal function CImageData::_SaveImages");
  1523. GUID guidFmt = *pguidFmt;
  1524. if (_ppbEncoderParams)
  1525. {
  1526. VARIANT var = {0};
  1527. // read the encoder format to be used
  1528. if (SUCCEEDED(_ppbEncoderParams->Read(SHIMGKEY_RAWFORMAT, &var, NULL)))
  1529. {
  1530. VariantToGUID(&var, &guidFmt);
  1531. VariantClear(&var);
  1532. }
  1533. // read the encoder quality to be used, this is set for the JPEG one only
  1534. if (guidFmt == ImageFormatJPEG)
  1535. {
  1536. SHPropertyBag_ReadInt(_ppbEncoderParams, SHIMGKEY_QUALITY, &iQuality);
  1537. iQuality = max(0, iQuality);
  1538. iQuality = min(100, iQuality);
  1539. }
  1540. }
  1541. // given the format GUID lets determine the encoder we intend to
  1542. // use to save the image
  1543. CLSID clsidEncoder;
  1544. hr = _GetEncoderFromFormat(&guidFmt, &clsidEncoder);
  1545. if (SUCCEEDED(hr))
  1546. {
  1547. // the way encoding works with GDI+ is a bit strange, you first need to call an image to
  1548. // have it save into a particular stream/file. if the image is multi-page then you
  1549. // must set an encoder parameter which defines that this will be a multi-page save (and
  1550. // that you will be calling the SaveAdd later).
  1551. //
  1552. // having performed the initial save, you must then attempt to add the subsequent pages
  1553. // to the file by calling SaveAdd, you call that method on the first image you saved
  1554. // specifying that you are adding another page (and possibly that this is the last image
  1555. // in the series).
  1556. BOOL bSaveCurrentOnly = FALSE;
  1557. Image *pimgFirstSave = NULL;
  1558. DWORD dwMaxPage = _cImages;
  1559. DWORD dwMinPage = 0;
  1560. // If viewing a multipage image and saving to a single page format, only save the current frame
  1561. if (_cImages > 1 && !FmtSupportsMultiPage(this, &guidFmt))
  1562. {
  1563. bSaveCurrentOnly = TRUE;
  1564. dwMaxPage = _iCurrent+1;
  1565. dwMinPage = _iCurrent;
  1566. }
  1567. for (DWORD i = dwMinPage; SUCCEEDED(hr) && (i < dwMaxPage); i++)
  1568. {
  1569. EncoderParameters ep[MAX_ENC_PARAMS] = { 0 };
  1570. ULONG ulCompression = 0; // in same scope as ep
  1571. // we use _pImage as the source if unedited in order to preserve properties
  1572. const CLSID * pclsidFrameDim = _fAnimated ? &FrameDimensionTime : &FrameDimensionPage;
  1573. _pImage->SelectActiveFrame(pclsidFrameDim, i);
  1574. Image *pimg;
  1575. if (_pimgEdited && i==_iCurrent)
  1576. {
  1577. pimg = _pimgEdited;
  1578. }
  1579. else
  1580. {
  1581. pimg = _pImage;
  1582. }
  1583. _SaveFrameProperties(pimg, i);
  1584. if (guidFmt == ImageFormatTIFF)
  1585. {
  1586. VARIANT var = {0};
  1587. if (SUCCEEDED(_GetProperty(PropertyTagCompression, &var, VT_UI2)))
  1588. {
  1589. // be sure to preserve TIFF compression
  1590. // these values are taken from the TIFF spec
  1591. switch (var.uiVal)
  1592. {
  1593. case 1:
  1594. ulCompression = EncoderValueCompressionNone;
  1595. break;
  1596. case 2:
  1597. ulCompression = EncoderValueCompressionCCITT3;
  1598. break;
  1599. case 3:
  1600. ulCompression = EncoderValueCompressionCCITT4;
  1601. break;
  1602. case 5:
  1603. ulCompression = EncoderValueCompressionLZW;
  1604. break;
  1605. case 32773:
  1606. ulCompression = EncoderValueCompressionRle;
  1607. break;
  1608. default:
  1609. // use the GDI+ default
  1610. break;
  1611. }
  1612. VariantClear(&var);
  1613. if (ulCompression)
  1614. {
  1615. _AddEncParameter(ep, EncoderCompression, EncoderParameterValueTypeLong, &ulCompression);
  1616. }
  1617. }
  1618. }
  1619. if (i == dwMinPage)
  1620. {
  1621. // we are writing the first page of the image, if this is a multi-page
  1622. // image then we need to set the encoder parameters accordingly (eg. set to
  1623. // multi-page).
  1624. ULONG ulValue = 0; // This needs to be in scope when Save is called
  1625. // We can only to lossless rotation when:
  1626. // * The original image is a JPEG file
  1627. // * The destination image is a JPEG file
  1628. // * We are only rotating and not scaling
  1629. // * The width and height of the JPEG are multiples of 8
  1630. // * Quality is unchanged by the caller
  1631. if (!_fDestructive &&
  1632. IsEqualIID(_guidFmt, ImageFormatJPEG) &&
  1633. IsEqualIID(guidFmt, ImageFormatJPEG) &&
  1634. (iQuality == 0))
  1635. {
  1636. // this code assumes JPEG files are single page since it's inside the i==0 case
  1637. ASSERT(_cImages == 1);
  1638. // for JPEG when doing only a rotate we use a special encoder parameter on the original
  1639. // image rather than using the edit image. This allows lossless rotation.
  1640. pimg = _pImage;
  1641. switch (_dwRotation)
  1642. {
  1643. case 90:
  1644. ulValue = EncoderValueTransformRotate90;
  1645. break;
  1646. case 180:
  1647. ulValue = EncoderValueTransformRotate180;
  1648. break;
  1649. case 270:
  1650. ulValue = EncoderValueTransformRotate270;
  1651. break;
  1652. }
  1653. _AddEncParameter(ep, EncoderTransformation, EncoderParameterValueTypeLong, &ulValue);
  1654. }
  1655. else if (_cImages > 1 && !bSaveCurrentOnly)
  1656. {
  1657. ulValue = EncoderValueMultiFrame;
  1658. _AddEncParameter(ep, EncoderSaveFlag, EncoderParameterValueTypeLong, &ulValue);
  1659. pimgFirstSave = pimg; // keep this image as we will us it for appending pages
  1660. }
  1661. // JPEG quality is only ever set for a single image, therefore don't
  1662. // bother passing it for the multi page case.
  1663. if (iQuality > 0)
  1664. _AddEncParameter(ep, EncoderQuality, EncoderParameterValueTypeLong, &iQuality);
  1665. hr = HR_FROM_STATUS(pimg->Save(pstrm, &clsidEncoder, (ep->Count > 0) ? ep:NULL));
  1666. }
  1667. else
  1668. {
  1669. // writing the next image in the series, set the encoding parameter
  1670. // to indicate that this is the next page. if we are writing the last
  1671. // image then set the last frame flag.
  1672. ULONG flagValueDim = EncoderValueFrameDimensionPage;
  1673. ULONG flagValueLastFrame = EncoderValueLastFrame;
  1674. _AddEncParameter(ep, EncoderSaveFlag, EncoderParameterValueTypeLong, &flagValueDim);
  1675. if (i == (dwMaxPage-1))
  1676. _AddEncParameter(ep, EncoderSaveFlag, EncoderParameterValueTypeLong, &flagValueLastFrame);
  1677. hr = HR_FROM_STATUS(pimgFirstSave->SaveAdd(pimg, (ep->Count > 0) ? ep:NULL));
  1678. }
  1679. }
  1680. }
  1681. if (SUCCEEDED(hr))
  1682. {
  1683. _fPropertyChanged = FALSE;
  1684. DiscardEdit();
  1685. }
  1686. // Suspend the stream so we don't leave the file open
  1687. _SuspendStream();
  1688. return hr;
  1689. }
  1690. // returns the DPI of the image
  1691. STDMETHODIMP CImageData::GetResolution(ULONG *puResolutionX, ULONG *puResolutionY)
  1692. {
  1693. if (!puResolutionX && !puResolutionY)
  1694. {
  1695. return E_INVALIDARG;
  1696. }
  1697. HRESULT hr = _EnsureImage();
  1698. if (puResolutionX)
  1699. {
  1700. *puResolutionX = 0;
  1701. }
  1702. if (puResolutionY)
  1703. {
  1704. *puResolutionY = 0;
  1705. }
  1706. if (SUCCEEDED(hr))
  1707. {
  1708. UINT uFlags = _pImage->GetFlags();
  1709. //
  1710. // We only return the DPI information from the image header for TIFFs whose
  1711. // X and Y DPI differ, those images are likely faxes.
  1712. // We want our client applications (slideshow, image preview)
  1713. // to deal with actual pixel sizes for the most part
  1714. //
  1715. ULONG resX = (ULONG)_pImage->GetHorizontalResolution();
  1716. ULONG resY = (ULONG)_pImage->GetVerticalResolution();
  1717. #ifndef USE_EMBEDDED_DPI_ALWAYS
  1718. if (_guidFmt != ImageFormatTIFF || !(uFlags & ImageFlagsHasRealDPI) || resX == resY )
  1719. {
  1720. // if GetDC fails we have to rely on the numbers back from GDI+
  1721. HDC hdc = GetDC(NULL);
  1722. if (hdc)
  1723. {
  1724. resX = GetDeviceCaps(hdc, LOGPIXELSX);
  1725. resY = GetDeviceCaps(hdc, LOGPIXELSY);
  1726. ReleaseDC(NULL, hdc);
  1727. }
  1728. }
  1729. #endif
  1730. if (puResolutionX)
  1731. {
  1732. *puResolutionX = resX;
  1733. }
  1734. if (puResolutionY)
  1735. {
  1736. *puResolutionY = resY;
  1737. }
  1738. if ((puResolutionX && !*puResolutionX) || (puResolutionY && !*puResolutionY))
  1739. {
  1740. hr = E_FAIL;
  1741. }
  1742. }
  1743. return hr;
  1744. }
  1745. // handle saving and replacing the original file
  1746. // in the case of replacing an existing file, we want the temp file to be in the same volume
  1747. // as the target
  1748. HRESULT CImageData::_MakeTempFile(LPWSTR pszFile)
  1749. {
  1750. ASSERT(_pstrm);
  1751. WCHAR szTempPath[MAX_PATH];
  1752. HRESULT hr = S_OK;
  1753. if (_pstrm->IsFileStream())
  1754. {
  1755. StrCpyN(szTempPath, _pstrm->GetFilename(), ARRAYSIZE(szTempPath));
  1756. PathRemoveFileSpec(szTempPath);
  1757. }
  1758. else if (!GetTempPath(ARRAYSIZE(szTempPath), szTempPath))
  1759. {
  1760. hr = E_FAIL;
  1761. }
  1762. if (SUCCEEDED(hr))
  1763. {
  1764. // SIV == "Shell Image Viewer"
  1765. if (GetTempFileName(szTempPath, TEXT("SIV"), 0, pszFile))
  1766. {
  1767. SetFileAttributes(pszFile, ATTRIBUTES_TEMPFILE);
  1768. // we need to suppress the change notfy from the GetTempFileName()
  1769. // call as that causes defview to display this.
  1770. // but for some reason it does not work
  1771. SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, pszFile, NULL);
  1772. }
  1773. else
  1774. {
  1775. hr = E_FAIL;
  1776. }
  1777. }
  1778. return hr;
  1779. }
  1780. HRESULT CImageData::_ReplaceFile(LPCTSTR pszNewFile)
  1781. {
  1782. // first we get some info about the file we're replacing:
  1783. LPCTSTR pszOldFile = _pstrm->GetFilename();
  1784. STATSTG ss = {0};
  1785. _pstrm->Stat(&ss, STATFLAG_NONAME);
  1786. // This ensures that the source handle is closed
  1787. _SuspendStream();
  1788. HRESULT hr;
  1789. // ReplaceFile doesn't save the modified time, so if we rotate an image twice in quick succession
  1790. // we won't add a full 2 seconds to the modified time. So query the time before replacing the file.
  1791. WIN32_FIND_DATA wfd = {0};
  1792. GetFileAttributesEx(pszOldFile, GetFileExInfoStandard, &wfd);
  1793. if (ReplaceFile(pszOldFile, pszNewFile, NULL, REPLACEFILE_WRITE_THROUGH, NULL, NULL))
  1794. {
  1795. // The old file has been replaced with the new file, but now we need to ensure that the
  1796. // filetime actually changed due to the 2 sec accuracy of FAT.
  1797. // we do this on NTFS too, since XP pidls have 2 sec accuracy since they cast the filetime
  1798. // down to a dos datetime.
  1799. HANDLE hFile = CreateFile(pszOldFile, GENERIC_READ | GENERIC_WRITE,
  1800. FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
  1801. NULL, OPEN_EXISTING, 0, NULL);
  1802. if (INVALID_HANDLE_VALUE != hFile)
  1803. {
  1804. FILETIME *pft = (CompareFileTime(&wfd.ftLastWriteTime, &ss.mtime) < 0) ? &ss.mtime : &wfd.ftLastWriteTime;
  1805. IncrementFILETIME(pft, 2 * FT_ONESECOND);
  1806. SetFileTime(hFile, NULL, NULL, pft);
  1807. CloseHandle(hFile);
  1808. }
  1809. // the replacefile call wont always keep the "replaced" file (pszOldFile) attributes, if it's
  1810. // replacing across a win98 share for example. no biggie, just set the attributes again, using
  1811. // the attribs we know we got from the stat.
  1812. SetFileAttributes(pszOldFile, ss.reserved);
  1813. SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT | SHCNF_FLUSH, pszOldFile, NULL);
  1814. hr = S_OK;
  1815. }
  1816. else
  1817. {
  1818. hr = HRESULT_FROM_WIN32(GetLastError());
  1819. }
  1820. return hr;
  1821. }
  1822. void SaveProperties(IPropertySetStorage *pss, Image *pimg, REFFMTID fmtid, CDSA<SHCOLUMNID> *pdsaChanges)
  1823. {
  1824. IPropertyStorage *pps;
  1825. if (SUCCEEDED(pss->Open(fmtid, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, &pps)))
  1826. {
  1827. CImagePropSet *pips = new CImagePropSet(pimg, NULL, pps, fmtid);
  1828. if (pips)
  1829. {
  1830. pips->SaveProps(pimg, pdsaChanges);
  1831. pips->Release();
  1832. }
  1833. pps->Release();
  1834. }
  1835. }
  1836. void CImageData::_SaveFrameProperties(Image *pimg, LONG iFrame)
  1837. {
  1838. // make sure _dsaChangedProps is non-NULL
  1839. if (_hdpaProps && (HDSA)_dsaChangedProps)
  1840. {
  1841. IPropertySetStorage *pss = (IPropertySetStorage *)DPA_GetPtr(_hdpaProps, iFrame);
  1842. if (pss)
  1843. {
  1844. // Start with FMTID_ImageProperties to make sure other FMTIDs take precedence (last one wins)
  1845. SaveProperties(pss, pimg, FMTID_ImageProperties, &_dsaChangedProps);
  1846. // enum all the property storages and create a CImagePropSet for each one
  1847. // and have it save to the pimg
  1848. IEnumSTATPROPSETSTG *penum;
  1849. if (SUCCEEDED(pss->Enum(&penum)))
  1850. {
  1851. STATPROPSETSTG spss;
  1852. while (S_OK == penum->Next(1, &spss, NULL))
  1853. {
  1854. if (!IsEqualGUID(spss.fmtid, FMTID_ImageProperties))
  1855. {
  1856. SaveProperties(pss, pimg, spss.fmtid, &_dsaChangedProps);
  1857. }
  1858. }
  1859. penum->Release();
  1860. }
  1861. }
  1862. }
  1863. }
  1864. void CImageData::_PropertyChanged(IShellImageData* pThis, SHCOLUMNID *pscid)
  1865. {
  1866. ((CImageData*)pThis)->_fPropertyChanged = TRUE;
  1867. if ((HDSA)(((CImageData*)pThis)->_dsaChangedProps))
  1868. {
  1869. ((CImageData*)pThis)->_dsaChangedProps.AppendItem(pscid);
  1870. }
  1871. }
  1872. //
  1873. // This function determines the list of available encoder parameters given the file format
  1874. // Hopefully future versions of GDI+ will decouple this call from the Image() object
  1875. // Don't call this function until ready to save the loaded image
  1876. STDMETHODIMP CImageData::GetEncoderParams(GUID *pguidFmt, EncoderParameters **ppencParams)
  1877. {
  1878. CLSID clsidEncoder;
  1879. HRESULT hr = E_FAIL;
  1880. if (_pImage && ppencParams)
  1881. {
  1882. hr = _GetEncoderFromFormat(pguidFmt, &clsidEncoder);
  1883. }
  1884. if (SUCCEEDED(hr))
  1885. {
  1886. hr = E_FAIL;
  1887. UINT uSize = _pImage->GetEncoderParameterListSize(&clsidEncoder);
  1888. if (uSize)
  1889. {
  1890. *ppencParams = (EncoderParameters *)CoTaskMemAlloc(uSize);
  1891. if (*ppencParams)
  1892. {
  1893. hr = HR_FROM_STATUS(_pImage->GetEncoderParameterList(&clsidEncoder, uSize, *ppencParams));
  1894. if (FAILED(hr))
  1895. {
  1896. CoTaskMemFree(*ppencParams);
  1897. *ppencParams = NULL;
  1898. }
  1899. }
  1900. }
  1901. }
  1902. return hr;
  1903. }
  1904. STDMETHODIMP CImageData::RegisterAbort(IShellImageDataAbort *pAbort, IShellImageDataAbort **ppAbortPrev)
  1905. {
  1906. if (ppAbortPrev)
  1907. {
  1908. *ppAbortPrev = _pAbort; // Transfer ownership to caller
  1909. }
  1910. else if (_pAbort)
  1911. {
  1912. _pAbort->Release(); // Caller doesn't want it, so throw away
  1913. }
  1914. _pAbort = pAbort; // Set the new abort callback
  1915. if (_pAbort)
  1916. {
  1917. _pAbort->AddRef();
  1918. }
  1919. return S_OK;
  1920. }
  1921. BOOL CALLBACK CImageData::QueryAbort(void *pvRef)
  1922. {
  1923. CImageData* pThis = reinterpret_cast<CImageData *>(pvRef);
  1924. return pThis->_pAbort && pThis->_pAbort->QueryAbort() == S_FALSE;
  1925. }
  1926. HRESULT CImageData::CloneFrame(Image **ppimg)
  1927. {
  1928. *ppimg = NULL;
  1929. Image *pimg = _pimgEdited ? _pimgEdited : _pImage;
  1930. if (pimg)
  1931. {
  1932. *ppimg = pimg->Clone();
  1933. }
  1934. return *ppimg ? S_OK : E_FAIL;
  1935. }
  1936. HRESULT CImageData::ReplaceFrame(Image *pimg)
  1937. {
  1938. _SetEditImage(pimg);
  1939. return S_OK;
  1940. }
  1941. /////////////////////////////////////////////////////////////////////////////////////////////////
  1942. // CImageDataFactory
  1943. /////////////////////////////////////////////////////////////////////////////////////////////////
  1944. STDAPI CImageDataFactory_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
  1945. {
  1946. CImageFactory *psid = new CImageFactory();
  1947. if (!psid)
  1948. {
  1949. *ppunk = NULL; // incase of failure
  1950. return E_OUTOFMEMORY;
  1951. }
  1952. HRESULT hr = psid->QueryInterface(IID_PPV_ARG(IUnknown, ppunk));
  1953. psid->Release();
  1954. return hr;
  1955. }
  1956. CImageFactory::CImageFactory() : _cRef(1)
  1957. {
  1958. _Module.Lock();
  1959. }
  1960. CImageFactory::~CImageFactory()
  1961. {
  1962. _Module.Unlock();
  1963. }
  1964. STDMETHODIMP CImageFactory::QueryInterface(REFIID riid, void **ppv)
  1965. {
  1966. static const QITAB qit[] =
  1967. {
  1968. QITABENT(CImageFactory, IShellImageDataFactory),
  1969. { 0 },
  1970. };
  1971. return QISearch(this, qit, riid, ppv);
  1972. }
  1973. STDMETHODIMP_(ULONG) CImageFactory::AddRef()
  1974. {
  1975. return InterlockedIncrement(&_cRef);
  1976. }
  1977. STDMETHODIMP_(ULONG) CImageFactory::Release()
  1978. {
  1979. if (InterlockedDecrement(&_cRef))
  1980. return _cRef;
  1981. delete this;
  1982. return 0;
  1983. }
  1984. HRESULT CImageFactory::CreateIShellImageData(IShellImageData **ppshimg)
  1985. {
  1986. CImageData *psid = new CImageData();
  1987. if (!psid)
  1988. return E_OUTOFMEMORY;
  1989. HRESULT hr = psid->QueryInterface(IID_PPV_ARG(IShellImageData, ppshimg));
  1990. psid->Release();
  1991. return hr;
  1992. }
  1993. HRESULT CImageFactory::CreateImageFromFile(LPCWSTR pszPath, IShellImageData **ppshimg)
  1994. {
  1995. HRESULT hr = E_OUTOFMEMORY;
  1996. CImageData *psid = new CImageData();
  1997. if (psid)
  1998. {
  1999. IPersistFile *ppf;
  2000. hr = psid->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
  2001. if (SUCCEEDED(hr))
  2002. {
  2003. hr = ppf->Load(pszPath, STGM_READ);
  2004. ppf->Release();
  2005. }
  2006. if (SUCCEEDED(hr))
  2007. hr = psid->QueryInterface(IID_PPV_ARG(IShellImageData, ppshimg));
  2008. psid->Release();
  2009. }
  2010. return hr;
  2011. }
  2012. HRESULT CImageFactory::CreateImageFromStream(IStream *pstrm, IShellImageData **ppshimg)
  2013. {
  2014. HRESULT hr = E_OUTOFMEMORY;
  2015. CImageData *psid = new CImageData();
  2016. if (psid)
  2017. {
  2018. IPersistStream *ppstrm;
  2019. hr = psid->QueryInterface(IID_PPV_ARG(IPersistStream, &ppstrm));
  2020. if (SUCCEEDED(hr))
  2021. {
  2022. hr = ppstrm->Load(pstrm);
  2023. ppstrm->Release();
  2024. }
  2025. if (SUCCEEDED(hr))
  2026. hr = psid->QueryInterface(IID_PPV_ARG(IShellImageData, ppshimg));
  2027. psid->Release();
  2028. }
  2029. return hr;
  2030. }
  2031. HRESULT CImageFactory::GetDataFormatFromPath(LPCWSTR pszPath, GUID *pguidFmt)
  2032. {
  2033. return _GetDataFormatFromPath(pszPath, pguidFmt);
  2034. }
  2035. HRESULT CEncoderInfo::_GetDataFormatFromPath(LPCWSTR pszPath, GUID *pguidFmt)
  2036. {
  2037. *pguidFmt = GUID_NULL;
  2038. HRESULT hr = _GetEncoderList();
  2039. if (SUCCEEDED(hr))
  2040. {
  2041. UINT i = FindInDecoderList(_pici, _cEncoders, pszPath);
  2042. if (-1 != i)
  2043. {
  2044. *pguidFmt = _pici[i].FormatID;
  2045. hr = S_OK;
  2046. }
  2047. else
  2048. {
  2049. hr = E_FAIL;
  2050. }
  2051. }
  2052. return hr;
  2053. }
  2054. HRESULT CEncoderInfo::_GetEncoderList()
  2055. {
  2056. HRESULT hr = S_OK;
  2057. if (!_pici)
  2058. {
  2059. // lets pick up the list of encoders, first we get the encoder size which
  2060. // gives us the CB and the number of encoders that are installed on the
  2061. // machine.
  2062. UINT cb;
  2063. hr = HR_FROM_STATUS(GetImageEncodersSize(&_cEncoders, &cb));
  2064. if (SUCCEEDED(hr))
  2065. {
  2066. // allocate the buffer for the encoders and then fill it
  2067. // with the encoder list.
  2068. _pici = (ImageCodecInfo*)LocalAlloc(LPTR, cb);
  2069. if (_pici)
  2070. {
  2071. hr = HR_FROM_STATUS(GetImageEncoders(_cEncoders, cb, _pici));
  2072. if (FAILED(hr))
  2073. {
  2074. LocalFree(_pici);
  2075. _pici = NULL;
  2076. }
  2077. }
  2078. else
  2079. {
  2080. hr = E_OUTOFMEMORY;
  2081. }
  2082. }
  2083. }
  2084. return hr;
  2085. }
  2086. HRESULT CEncoderInfo::_GetEncoderFromFormat(const GUID *pfmt, CLSID *pclsidEncoder)
  2087. {
  2088. HRESULT hr = _GetEncoderList();
  2089. if (SUCCEEDED(hr))
  2090. {
  2091. hr = E_FAIL;
  2092. for (UINT i = 0; i != _cEncoders; i++)
  2093. {
  2094. if (_pici[i].FormatID == *pfmt)
  2095. {
  2096. if (pclsidEncoder)
  2097. {
  2098. *pclsidEncoder = _pici[i].Clsid; // return the CLSID of the encoder so we can create again
  2099. }
  2100. hr = S_OK;
  2101. break;
  2102. }
  2103. }
  2104. }
  2105. return hr;
  2106. }
  2107. CEncoderInfo::CEncoderInfo()
  2108. {
  2109. _pici = NULL;
  2110. _cEncoders = 0;
  2111. }
  2112. CEncoderInfo::~CEncoderInfo()
  2113. {
  2114. if (_pici)
  2115. LocalFree(_pici); // do we have an encoder array to be destroyed
  2116. }
  2117. STDAPI CImageData_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
  2118. {
  2119. CImageData *psid = new CImageData(*(poi->pclsid) == CLSID_ImagePropertyHandler);
  2120. if (!psid)
  2121. {
  2122. *ppunk = NULL; // incase of failure
  2123. return E_OUTOFMEMORY;
  2124. }
  2125. HRESULT hr = psid->QueryInterface(IID_PPV_ARG(IUnknown, ppunk));
  2126. psid->Release();
  2127. return hr;
  2128. }
  2129. int CImageData::_FreeProps(void* pProp, void* pData)
  2130. {
  2131. if (pProp)
  2132. {
  2133. ((IPropertySetStorage*)pProp)->Release();
  2134. }
  2135. return 1;
  2136. }
  2137. // Our CFmtEnum is a minimal enumerator to provide FMTID_SummaryInformation in our
  2138. // formats. It's optimized for 1-by-1 enumeration
  2139. STDMETHODIMP CFmtEnum::Next(ULONG celt, STATPROPSETSTG *rgelt, ULONG *pceltFetched)
  2140. {
  2141. HRESULT hr = S_OK;
  2142. if (pceltFetched)
  2143. {
  2144. *pceltFetched = 0;
  2145. }
  2146. if (!celt || !rgelt)
  2147. {
  2148. hr = E_INVALIDARG;
  2149. }
  2150. else if (0 == _idx)
  2151. {
  2152. ZeroMemory(rgelt, sizeof(*rgelt));
  2153. rgelt->fmtid = FMTID_ImageSummaryInformation;
  2154. rgelt->grfFlags = STGM_READ | STGM_SHARE_DENY_NONE;
  2155. if (pceltFetched)
  2156. {
  2157. *pceltFetched = 1;
  2158. }
  2159. _idx++;
  2160. celt--;
  2161. rgelt++;
  2162. }
  2163. if (SUCCEEDED(hr) && celt)
  2164. {
  2165. ULONG ul;
  2166. hr = _pEnum->Next(celt, rgelt, &ul);
  2167. if (SUCCEEDED(hr) && pceltFetched)
  2168. {
  2169. (*pceltFetched) += ul;
  2170. }
  2171. }
  2172. return hr;
  2173. }
  2174. STDMETHODIMP CFmtEnum::Skip(ULONG celt)
  2175. {
  2176. HRESULT hr = S_OK;
  2177. if (_idx == 0)
  2178. {
  2179. _idx++;
  2180. celt--;
  2181. }
  2182. if (celt)
  2183. {
  2184. hr = _pEnum->Skip(celt);
  2185. }
  2186. return hr;
  2187. }
  2188. STDMETHODIMP CFmtEnum::Reset(void)
  2189. {
  2190. _idx = 0;
  2191. return _pEnum->Reset();
  2192. }
  2193. STDMETHODIMP CFmtEnum::Clone(IEnumSTATPROPSETSTG **ppenum)
  2194. {
  2195. HRESULT hr = E_OUTOFMEMORY;
  2196. CFmtEnum *pNew = new CFmtEnum(_pEnum);
  2197. if (pNew)
  2198. {
  2199. hr = pNew->QueryInterface(IID_PPV_ARG(IEnumSTATPROPSETSTG, ppenum));
  2200. pNew->Release();
  2201. }
  2202. return hr;
  2203. }
  2204. STDMETHODIMP CFmtEnum::QueryInterface(REFIID riid, void **ppvObj)
  2205. {
  2206. static const QITAB qit[] =
  2207. {
  2208. QITABENT(CFmtEnum, IEnumSTATPROPSETSTG),
  2209. { 0 },
  2210. };
  2211. return QISearch(this, qit, riid, ppvObj);
  2212. }
  2213. STDMETHODIMP_(ULONG) CFmtEnum::AddRef()
  2214. {
  2215. return InterlockedIncrement(&_cRef);
  2216. }
  2217. STDMETHODIMP_(ULONG) CFmtEnum::Release()
  2218. {
  2219. if (InterlockedDecrement(&_cRef))
  2220. return _cRef;
  2221. delete this;
  2222. return 0;
  2223. }
  2224. CFmtEnum::CFmtEnum(IEnumSTATPROPSETSTG *pEnum) : _cRef(1), _idx(0), _pEnum(pEnum)
  2225. {
  2226. _pEnum->AddRef();
  2227. }
  2228. CFmtEnum::~CFmtEnum()
  2229. {
  2230. _pEnum->Release();
  2231. }