313 lines
9.7 KiB
C++
313 lines
9.7 KiB
C++
|
#include "precomp.h"
|
||
|
#include "prevwnd.h"
|
||
|
#pragma hdrstop
|
||
|
|
||
|
|
||
|
// class which implements IRecompress
|
||
|
|
||
|
class CImgRecompress : public IImageRecompress, public NonATLObject
|
||
|
{
|
||
|
public:
|
||
|
CImgRecompress();
|
||
|
~CImgRecompress();
|
||
|
|
||
|
// IUnknown
|
||
|
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
|
||
|
STDMETHOD_(ULONG, AddRef)();
|
||
|
STDMETHOD_(ULONG, Release)();
|
||
|
|
||
|
// IImageRecompress
|
||
|
STDMETHODIMP RecompressImage(IShellItem *psi, int cx, int cy, int iQuality, IStorage *pstg, IStream **ppstrmOut);
|
||
|
|
||
|
protected:
|
||
|
LONG _cRef; // object lifetime
|
||
|
|
||
|
IShellItem *_psi; // current shell item
|
||
|
IShellImageDataFactory *_psidf;
|
||
|
|
||
|
HRESULT _FindEncoder(IShellItem *psi, IShellImageData *psid, IStorage *pstg, IStream **ppstrmOut, BOOL *pfChangeFmt, GUID *pDataFormat);
|
||
|
HRESULT _InitRecompress(IShellItem *psi, IStream **ppstrm, STATSTG *pstatIn);
|
||
|
HRESULT _SaveImage(IShellImageData *psid, int cx, int cy, int iQuality, GUID *pRawDataFmt, IStream *pstrm);
|
||
|
};
|
||
|
|
||
|
|
||
|
// Recompress interface
|
||
|
CImgRecompress::CImgRecompress() :
|
||
|
_cRef(1), _psidf(NULL)
|
||
|
{
|
||
|
_Module.Lock();
|
||
|
}
|
||
|
|
||
|
CImgRecompress::~CImgRecompress()
|
||
|
{
|
||
|
ATOMICRELEASE(_psidf);
|
||
|
_Module.Unlock();
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP CImgRecompress::QueryInterface(REFIID riid, void **ppv)
|
||
|
{
|
||
|
static const QITAB qit[] =
|
||
|
{
|
||
|
QITABENT(CImgRecompress, IImageRecompress),
|
||
|
{ 0 },
|
||
|
};
|
||
|
return QISearch(this, qit, riid, ppv);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CImgRecompress::AddRef()
|
||
|
{
|
||
|
return InterlockedIncrement(&_cRef);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) CImgRecompress::Release()
|
||
|
{
|
||
|
if (InterlockedDecrement(&_cRef))
|
||
|
return _cRef;
|
||
|
|
||
|
delete this;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT CImgRecompress::_InitRecompress(IShellItem *psi, IStream **ppstrm, STATSTG *pstatIn)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
if (!_psidf)
|
||
|
hr = CoCreateInstance(CLSID_ShellImageDataFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellImageDataFactory, &_psidf));
|
||
|
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
IBindCtx *pbc;
|
||
|
hr = BindCtx_CreateWithMode(STGM_READ | STGM_SHARE_DENY_NONE, &pbc);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
IStream *pstrm;
|
||
|
hr = psi->BindToHandler(pbc, BHID_Stream, IID_PPV_ARG(IStream, &pstrm));
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = pstrm->Stat(pstatIn, STATFLAG_NONAME);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = pstrm->QueryInterface(IID_PPV_ARG(IStream, ppstrm));
|
||
|
}
|
||
|
pstrm->Release();
|
||
|
}
|
||
|
pbc->Release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT CImgRecompress::RecompressImage(IShellItem *psi, int cx, int cy, int iQuality, IStorage *pstg, IStream **ppstrmOut)
|
||
|
{
|
||
|
STATSTG statIn;
|
||
|
IStream *pstrm;
|
||
|
|
||
|
HRESULT hr = S_FALSE;
|
||
|
if (SUCCEEDED(_InitRecompress(psi, &pstrm, &statIn)))
|
||
|
{
|
||
|
IShellImageData * psid;
|
||
|
if (SUCCEEDED(_psidf->CreateImageFromStream(pstrm, &psid)))
|
||
|
{
|
||
|
// we need to decode the image before we can read its header - unfortunately
|
||
|
if (SUCCEEDED(psid->Decode(SHIMGDEC_DEFAULT, 0, 0)))
|
||
|
{
|
||
|
BOOL fRecompress = FALSE;
|
||
|
GUID guidDataFormat;
|
||
|
if (S_OK == _FindEncoder(psi, psid, pstg, ppstrmOut, &fRecompress, &guidDataFormat))
|
||
|
{
|
||
|
int cxOut = 0, cyOut = 0;
|
||
|
|
||
|
// lets compute to see if we need to recompress the image, we do this by
|
||
|
// looking at its size compared ot the size the caller has given us,
|
||
|
// we also compare based on the larger axis to ensure we keep aspect ratio.
|
||
|
|
||
|
SIZE szImage;
|
||
|
if (SUCCEEDED(psid->GetSize(&szImage)))
|
||
|
{
|
||
|
// If the image is too big scale it down to screen size (use large axis for threshold check)
|
||
|
if (szImage.cx > szImage.cy)
|
||
|
{
|
||
|
cxOut = min(szImage.cx, cx);
|
||
|
fRecompress |= szImage.cx > cx;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cyOut = min(szImage.cy, cy);
|
||
|
fRecompress |= szImage.cy > cy;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if fRecompress then we generate the new stream, if the new stream is not
|
||
|
// smaller than the current image that we started with then lets
|
||
|
// ignore it (always better to send the smaller of the two).
|
||
|
//
|
||
|
|
||
|
if (fRecompress)
|
||
|
{
|
||
|
hr = _SaveImage(psid, cxOut, cyOut, iQuality, &guidDataFormat, *ppstrmOut);
|
||
|
|
||
|
// If its not worth keeping then lets discard it (eg.
|
||
|
// there was a failure, or new image is bigger than the original.
|
||
|
|
||
|
// tests have shown that this never gets hit, so I'm removing it b/c it will break
|
||
|
// the web publishing wizard's request to resize to this specific size. a client
|
||
|
// of recompress can perform this check itself if it needs to.
|
||
|
#if 0
|
||
|
STATSTG statOut;
|
||
|
hr = (*ppstrmOut)->Stat(&statOut, STATFLAG_NONAME);
|
||
|
if (FAILED(hr) || (statOut.cbSize.QuadPart > statIn.cbSize.QuadPart))
|
||
|
{
|
||
|
hr = S_FALSE;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if (hr == S_OK)
|
||
|
{
|
||
|
(*ppstrmOut)->Commit(0); // commit our changes to the stream
|
||
|
|
||
|
LARGE_INTEGER li0 = {0}; // seek to the head of the file so reading gives us bits
|
||
|
(*ppstrmOut)->Seek(li0, 0, NULL);
|
||
|
}
|
||
|
else if (*ppstrmOut)
|
||
|
{
|
||
|
(*ppstrmOut)->Release();
|
||
|
*ppstrmOut = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
psid->Release();
|
||
|
}
|
||
|
pstrm->Release();
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT CImgRecompress::_SaveImage(IShellImageData *psid, int cx, int cy, int iQuality, GUID *pRawDataFmt, IStream *pstrm)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
// Scale the image
|
||
|
if (cx || cy)
|
||
|
{
|
||
|
hr = psid->Scale(cx, cy, InterpolationModeHighQuality);
|
||
|
}
|
||
|
|
||
|
// Make a property bag containing the encoder parameters and set it (if we are changing format)
|
||
|
if (SUCCEEDED(hr) && pRawDataFmt)
|
||
|
{
|
||
|
IPropertyBag *pbagEnc;
|
||
|
hr = SHCreatePropertyBagOnMemory(STGM_READWRITE, IID_PPV_ARG(IPropertyBag, &pbagEnc));
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
// write the encoder CLSID into the property bag
|
||
|
VARIANT var;
|
||
|
hr = InitVariantFromGUID(&var, *pRawDataFmt);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = pbagEnc->Write(SHIMGKEY_RAWFORMAT, &var);
|
||
|
VariantClear(&var);
|
||
|
}
|
||
|
|
||
|
// write the quality value for the recompression into the property bag
|
||
|
if (SUCCEEDED(hr))
|
||
|
hr = SHPropertyBag_WriteInt(pbagEnc, SHIMGKEY_QUALITY, iQuality);
|
||
|
|
||
|
// pass the parameters over to the encoder
|
||
|
if (SUCCEEDED(hr))
|
||
|
hr = psid->SetEncoderParams(pbagEnc);
|
||
|
|
||
|
pbagEnc->Release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Now persist the file away
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
IPersistStream *ppsImg;
|
||
|
hr = psid->QueryInterface(IID_PPV_ARG(IPersistStream, &ppsImg));
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = ppsImg->Save(pstrm, TRUE);
|
||
|
ppsImg->Release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT CImgRecompress::_FindEncoder(IShellItem *psi, IShellImageData *psid, IStorage *pstg, IStream **ppstrmOut, BOOL *pfChangeFmt, GUID *pDataFormat)
|
||
|
{
|
||
|
GUID guidDataFormat;
|
||
|
BOOL fChangeExt = FALSE;
|
||
|
|
||
|
// read the relative name from the stream so that we can create a temporary one which maps
|
||
|
LPWSTR pwszName;
|
||
|
HRESULT hr = psi->GetDisplayName(SIGDN_PARENTRELATIVEPARSING, &pwszName);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
// get the data format from the image we are decompressing
|
||
|
hr = psid->GetRawDataFormat(&guidDataFormat);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
if (!IsEqualGUID(guidDataFormat, ImageFormatJPEG))
|
||
|
{
|
||
|
// ask the image about it's properties
|
||
|
if ((S_FALSE == psid->IsMultipage()) &&
|
||
|
(S_FALSE == psid->IsVector()) &&
|
||
|
(S_FALSE == psid->IsTransparent()) &&
|
||
|
(S_FALSE == psid->IsAnimated()))
|
||
|
{
|
||
|
guidDataFormat = ImageFormatJPEG;
|
||
|
fChangeExt = TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = S_FALSE; // can't be translated
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// update the name accordingly before making a stream
|
||
|
TCHAR szOutName[MAX_PATH];
|
||
|
StrCpyNW(szOutName, pwszName, ARRAYSIZE(szOutName));
|
||
|
if (fChangeExt)
|
||
|
{
|
||
|
PathRenameExtension(szOutName, TEXT(".jpg"));
|
||
|
}
|
||
|
|
||
|
// TODO: need to get FILE_FLAG_DELETE_ON_CLOSE to happen on CreateFile
|
||
|
hr = StgMakeUniqueName(pstg, szOutName, IID_PPV_ARG(IStream, ppstrmOut));
|
||
|
}
|
||
|
|
||
|
CoTaskMemFree(pwszName);
|
||
|
}
|
||
|
|
||
|
if (pfChangeFmt)
|
||
|
*pfChangeFmt = fChangeExt;
|
||
|
|
||
|
*pDataFormat = guidDataFormat;
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
STDAPI CImgRecompress_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
|
||
|
{
|
||
|
CImgRecompress *pr = new CImgRecompress();
|
||
|
if (!pr)
|
||
|
{
|
||
|
*ppunk = NULL; // incase of failure
|
||
|
return E_OUTOFMEMORY;
|
||
|
}
|
||
|
|
||
|
HRESULT hr = pr->QueryInterface(IID_PPV_ARG(IUnknown, ppunk));
|
||
|
pr->Release();
|
||
|
return hr;
|
||
|
}
|