Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2599 lines
79 KiB

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