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.

312 lines
9.7 KiB

  1. #include "precomp.h"
  2. #include "prevwnd.h"
  3. #pragma hdrstop
  4. // class which implements IRecompress
  5. class CImgRecompress : public IImageRecompress, public NonATLObject
  6. {
  7. public:
  8. CImgRecompress();
  9. ~CImgRecompress();
  10. // IUnknown
  11. STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
  12. STDMETHOD_(ULONG, AddRef)();
  13. STDMETHOD_(ULONG, Release)();
  14. // IImageRecompress
  15. STDMETHODIMP RecompressImage(IShellItem *psi, int cx, int cy, int iQuality, IStorage *pstg, IStream **ppstrmOut);
  16. protected:
  17. LONG _cRef; // object lifetime
  18. IShellItem *_psi; // current shell item
  19. IShellImageDataFactory *_psidf;
  20. HRESULT _FindEncoder(IShellItem *psi, IShellImageData *psid, IStorage *pstg, IStream **ppstrmOut, BOOL *pfChangeFmt, GUID *pDataFormat);
  21. HRESULT _InitRecompress(IShellItem *psi, IStream **ppstrm, STATSTG *pstatIn);
  22. HRESULT _SaveImage(IShellImageData *psid, int cx, int cy, int iQuality, GUID *pRawDataFmt, IStream *pstrm);
  23. };
  24. // Recompress interface
  25. CImgRecompress::CImgRecompress() :
  26. _cRef(1), _psidf(NULL)
  27. {
  28. _Module.Lock();
  29. }
  30. CImgRecompress::~CImgRecompress()
  31. {
  32. ATOMICRELEASE(_psidf);
  33. _Module.Unlock();
  34. }
  35. STDMETHODIMP CImgRecompress::QueryInterface(REFIID riid, void **ppv)
  36. {
  37. static const QITAB qit[] =
  38. {
  39. QITABENT(CImgRecompress, IImageRecompress),
  40. { 0 },
  41. };
  42. return QISearch(this, qit, riid, ppv);
  43. }
  44. STDMETHODIMP_(ULONG) CImgRecompress::AddRef()
  45. {
  46. return InterlockedIncrement(&_cRef);
  47. }
  48. STDMETHODIMP_(ULONG) CImgRecompress::Release()
  49. {
  50. if (InterlockedDecrement(&_cRef))
  51. return _cRef;
  52. delete this;
  53. return 0;
  54. }
  55. HRESULT CImgRecompress::_InitRecompress(IShellItem *psi, IStream **ppstrm, STATSTG *pstatIn)
  56. {
  57. HRESULT hr = S_OK;
  58. if (!_psidf)
  59. hr = CoCreateInstance(CLSID_ShellImageDataFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellImageDataFactory, &_psidf));
  60. if (SUCCEEDED(hr))
  61. {
  62. IBindCtx *pbc;
  63. hr = BindCtx_CreateWithMode(STGM_READ | STGM_SHARE_DENY_NONE, &pbc);
  64. if (SUCCEEDED(hr))
  65. {
  66. IStream *pstrm;
  67. hr = psi->BindToHandler(pbc, BHID_Stream, IID_PPV_ARG(IStream, &pstrm));
  68. if (SUCCEEDED(hr))
  69. {
  70. hr = pstrm->Stat(pstatIn, STATFLAG_NONAME);
  71. if (SUCCEEDED(hr))
  72. {
  73. hr = pstrm->QueryInterface(IID_PPV_ARG(IStream, ppstrm));
  74. }
  75. pstrm->Release();
  76. }
  77. pbc->Release();
  78. }
  79. }
  80. return hr;
  81. }
  82. HRESULT CImgRecompress::RecompressImage(IShellItem *psi, int cx, int cy, int iQuality, IStorage *pstg, IStream **ppstrmOut)
  83. {
  84. STATSTG statIn;
  85. IStream *pstrm;
  86. HRESULT hr = S_FALSE;
  87. if (SUCCEEDED(_InitRecompress(psi, &pstrm, &statIn)))
  88. {
  89. IShellImageData * psid;
  90. if (SUCCEEDED(_psidf->CreateImageFromStream(pstrm, &psid)))
  91. {
  92. // we need to decode the image before we can read its header - unfortunately
  93. if (SUCCEEDED(psid->Decode(SHIMGDEC_DEFAULT, 0, 0)))
  94. {
  95. BOOL fRecompress = FALSE;
  96. GUID guidDataFormat;
  97. if (S_OK == _FindEncoder(psi, psid, pstg, ppstrmOut, &fRecompress, &guidDataFormat))
  98. {
  99. int cxOut = 0, cyOut = 0;
  100. // lets compute to see if we need to recompress the image, we do this by
  101. // looking at its size compared ot the size the caller has given us,
  102. // we also compare based on the larger axis to ensure we keep aspect ratio.
  103. SIZE szImage;
  104. if (SUCCEEDED(psid->GetSize(&szImage)))
  105. {
  106. // If the image is too big scale it down to screen size (use large axis for threshold check)
  107. if (szImage.cx > szImage.cy)
  108. {
  109. cxOut = min(szImage.cx, cx);
  110. fRecompress |= szImage.cx > cx;
  111. }
  112. else
  113. {
  114. cyOut = min(szImage.cy, cy);
  115. fRecompress |= szImage.cy > cy;
  116. }
  117. }
  118. // if fRecompress then we generate the new stream, if the new stream is not
  119. // smaller than the current image that we started with then lets
  120. // ignore it (always better to send the smaller of the two).
  121. //
  122. if (fRecompress)
  123. {
  124. hr = _SaveImage(psid, cxOut, cyOut, iQuality, &guidDataFormat, *ppstrmOut);
  125. // If its not worth keeping then lets discard it (eg.
  126. // there was a failure, or new image is bigger than the original.
  127. // tests have shown that this never gets hit, so I'm removing it b/c it will break
  128. // the web publishing wizard's request to resize to this specific size. a client
  129. // of recompress can perform this check itself if it needs to.
  130. #if 0
  131. STATSTG statOut;
  132. hr = (*ppstrmOut)->Stat(&statOut, STATFLAG_NONAME);
  133. if (FAILED(hr) || (statOut.cbSize.QuadPart > statIn.cbSize.QuadPart))
  134. {
  135. hr = S_FALSE;
  136. }
  137. #endif
  138. }
  139. if (hr == S_OK)
  140. {
  141. (*ppstrmOut)->Commit(0); // commit our changes to the stream
  142. LARGE_INTEGER li0 = {0}; // seek to the head of the file so reading gives us bits
  143. (*ppstrmOut)->Seek(li0, 0, NULL);
  144. }
  145. else if (*ppstrmOut)
  146. {
  147. (*ppstrmOut)->Release();
  148. *ppstrmOut = NULL;
  149. }
  150. }
  151. }
  152. psid->Release();
  153. }
  154. pstrm->Release();
  155. }
  156. return hr;
  157. }
  158. HRESULT CImgRecompress::_SaveImage(IShellImageData *psid, int cx, int cy, int iQuality, GUID *pRawDataFmt, IStream *pstrm)
  159. {
  160. HRESULT hr = S_OK;
  161. // Scale the image
  162. if (cx || cy)
  163. {
  164. hr = psid->Scale(cx, cy, InterpolationModeHighQuality);
  165. }
  166. // Make a property bag containing the encoder parameters and set it (if we are changing format)
  167. if (SUCCEEDED(hr) && pRawDataFmt)
  168. {
  169. IPropertyBag *pbagEnc;
  170. hr = SHCreatePropertyBagOnMemory(STGM_READWRITE, IID_PPV_ARG(IPropertyBag, &pbagEnc));
  171. if (SUCCEEDED(hr))
  172. {
  173. // write the encoder CLSID into the property bag
  174. VARIANT var;
  175. hr = InitVariantFromGUID(&var, *pRawDataFmt);
  176. if (SUCCEEDED(hr))
  177. {
  178. hr = pbagEnc->Write(SHIMGKEY_RAWFORMAT, &var);
  179. VariantClear(&var);
  180. }
  181. // write the quality value for the recompression into the property bag
  182. if (SUCCEEDED(hr))
  183. hr = SHPropertyBag_WriteInt(pbagEnc, SHIMGKEY_QUALITY, iQuality);
  184. // pass the parameters over to the encoder
  185. if (SUCCEEDED(hr))
  186. hr = psid->SetEncoderParams(pbagEnc);
  187. pbagEnc->Release();
  188. }
  189. }
  190. // Now persist the file away
  191. if (SUCCEEDED(hr))
  192. {
  193. IPersistStream *ppsImg;
  194. hr = psid->QueryInterface(IID_PPV_ARG(IPersistStream, &ppsImg));
  195. if (SUCCEEDED(hr))
  196. {
  197. hr = ppsImg->Save(pstrm, TRUE);
  198. ppsImg->Release();
  199. }
  200. }
  201. return hr;
  202. }
  203. HRESULT CImgRecompress::_FindEncoder(IShellItem *psi, IShellImageData *psid, IStorage *pstg, IStream **ppstrmOut, BOOL *pfChangeFmt, GUID *pDataFormat)
  204. {
  205. GUID guidDataFormat;
  206. BOOL fChangeExt = FALSE;
  207. // read the relative name from the stream so that we can create a temporary one which maps
  208. LPWSTR pwszName;
  209. HRESULT hr = psi->GetDisplayName(SIGDN_PARENTRELATIVEPARSING, &pwszName);
  210. if (SUCCEEDED(hr))
  211. {
  212. // get the data format from the image we are decompressing
  213. hr = psid->GetRawDataFormat(&guidDataFormat);
  214. if (SUCCEEDED(hr))
  215. {
  216. if (!IsEqualGUID(guidDataFormat, ImageFormatJPEG))
  217. {
  218. // ask the image about it's properties
  219. if ((S_FALSE == psid->IsMultipage()) &&
  220. (S_FALSE == psid->IsVector()) &&
  221. (S_FALSE == psid->IsTransparent()) &&
  222. (S_FALSE == psid->IsAnimated()))
  223. {
  224. guidDataFormat = ImageFormatJPEG;
  225. fChangeExt = TRUE;
  226. }
  227. else
  228. {
  229. hr = S_FALSE; // can't be translated
  230. }
  231. }
  232. // update the name accordingly before making a stream
  233. TCHAR szOutName[MAX_PATH];
  234. StrCpyNW(szOutName, pwszName, ARRAYSIZE(szOutName));
  235. if (fChangeExt)
  236. {
  237. PathRenameExtension(szOutName, TEXT(".jpg"));
  238. }
  239. // TODO: need to get FILE_FLAG_DELETE_ON_CLOSE to happen on CreateFile
  240. hr = StgMakeUniqueName(pstg, szOutName, IID_PPV_ARG(IStream, ppstrmOut));
  241. }
  242. CoTaskMemFree(pwszName);
  243. }
  244. if (pfChangeFmt)
  245. *pfChangeFmt = fChangeExt;
  246. *pDataFormat = guidDataFormat;
  247. return hr;
  248. }
  249. STDAPI CImgRecompress_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
  250. {
  251. CImgRecompress *pr = new CImgRecompress();
  252. if (!pr)
  253. {
  254. *ppunk = NULL; // incase of failure
  255. return E_OUTOFMEMORY;
  256. }
  257. HRESULT hr = pr->QueryInterface(IID_PPV_ARG(IUnknown, ppunk));
  258. pr->Release();
  259. return hr;
  260. }