#include "priv.h" #include #include "strsafe.h" class CImgCtxThumb : public IExtractImage2, public IRunnableTask, public IPersistFile { public: CImgCtxThumb(); ~CImgCtxThumb(); STDMETHOD(QueryInterface) (REFIID riid, void **ppvObj); STDMETHOD_(ULONG, AddRef) (void); STDMETHOD_(ULONG, Release) (void); // IExtractImage STDMETHOD (GetLocation) (LPWSTR pszPathBuffer, DWORD cch, DWORD * pdwPriority, const SIZE * prgSize, DWORD dwRecClrDepth, DWORD *pdwFlags); STDMETHOD (Extract)(HBITMAP * phBmpThumbnail); STDMETHOD (GetDateStamp) (FILETIME * pftTimeStamp); // IPersistFile STDMETHOD (GetClassID)(CLSID *pClassID); STDMETHOD (IsDirty)(); STDMETHOD (Load)(LPCOLESTR pszFileName, DWORD dwMode); STDMETHOD (Save)(LPCOLESTR pszFileName, BOOL fRemember); STDMETHOD (SaveCompleted)(LPCOLESTR pszFileName); STDMETHOD (GetCurFile)(LPOLESTR *ppszFileName); STDMETHOD (Run)(); STDMETHOD (Kill)(BOOL fWait); STDMETHOD (Suspend)(); STDMETHOD (Resume)(); STDMETHOD_(ULONG, IsRunning)(); STDMETHOD (InternalResume)(); protected: friend void CALLBACK OnImgCtxChange(void * pvImgCtx, void * pv); void CImgCtxThumb::CalcAspectScaledRect(const SIZE * prgSize, RECT * pRect); void CImgCtxThumb::CalculateAspectRatio(const SIZE * prgSize, RECT * pRect); long m_cRef; BITBOOL m_fAsync : 1; BITBOOL m_fOrigSize : 1; WCHAR m_szPath[MAX_PATH * 4 + 7]; HANDLE m_hEvent; SIZE m_rgSize; DWORD m_dwRecClrDepth; IImgCtx * m_pImg; LONG m_lState; HBITMAP * m_phBmp; }; STDAPI CImgCtxThumb_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi) { *ppunk = NULL; CImgCtxThumb * pExtract = new CImgCtxThumb(); if (pExtract != NULL) { *ppunk = SAFECAST(pExtract, IPersistFile *); return S_OK; } return E_OUTOFMEMORY; } CImgCtxThumb::CImgCtxThumb() { m_fAsync = FALSE; StringCchCopyW(m_szPath, ARRAYSIZE(m_szPath), L"file://"); m_cRef = 1; DllAddRef(); } CImgCtxThumb::~CImgCtxThumb() { ATOMICRELEASE(m_pImg); if (m_hEvent) { CloseHandle(m_hEvent); } DllRelease(); } STDMETHODIMP CImgCtxThumb::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENTMULTI(CImgCtxThumb, IExtractImage, IExtractImage2), QITABENT(CImgCtxThumb, IExtractImage2), QITABENT(CImgCtxThumb, IRunnableTask), QITABENT(CImgCtxThumb, IPersistFile), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } STDMETHODIMP_(ULONG) CImgCtxThumb::AddRef() { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) CImgCtxThumb::Release() { ASSERT( 0 != m_cRef ); ULONG cRef = InterlockedDecrement(&m_cRef); if ( 0 == cRef ) { delete this; } return cRef; } STDMETHODIMP CImgCtxThumb::GetLocation (LPWSTR pszPathBuffer, DWORD cch, DWORD * pdwPriority, const SIZE * prgSize, DWORD dwRecClrDepth, DWORD *pdwFlags) { if (!pdwFlags || !pszPathBuffer || !prgSize) { return E_INVALIDARG; } m_rgSize = *prgSize; m_dwRecClrDepth = dwRecClrDepth; HRESULT hr = S_OK; if (*pdwFlags & IEIFLAG_ASYNC) { if (!pdwPriority) { return E_INVALIDARG; } hr = E_PENDING; m_fAsync = TRUE; } m_fOrigSize = BOOLIFY(*pdwFlags & IEIFLAG_ORIGSIZE); *pdwFlags = IEIFLAG_CACHE; hr = PathCreateFromUrlW(m_szPath, pszPathBuffer, &cch, URL_UNESCAPE); return hr; } void CALLBACK OnImgCtxChange(void * pvImgCtx, void * pv) { CImgCtxThumb * pThis = (CImgCtxThumb *) pv; ASSERT(pThis); ASSERT(pThis->m_hEvent); // we only asked to know about complete anyway.... SetEvent(pThis->m_hEvent); } // This function makes no assumption about whether the thumbnail is square, so // it calculates the scaling ratio for both dimensions and the uses that as // the scaling to maintain the aspect ratio. void CImgCtxThumb::CalcAspectScaledRect(const SIZE * prgSize, RECT * pRect) { ASSERT(pRect->left == 0); ASSERT(pRect->top == 0); int iWidth = pRect->right; int iHeight = pRect->bottom; int iXRatio = (iWidth * 1000) / prgSize->cx; int iYRatio = (iHeight * 1000) / prgSize->cy; if (iXRatio > iYRatio) { pRect->right = prgSize->cx; // work out the blank space and split it evenly between the top and the bottom... int iNewHeight = ((iHeight * 1000) / iXRatio); if (iNewHeight == 0) { iNewHeight = 1; } int iRemainder = prgSize->cy - iNewHeight; pRect->top = iRemainder / 2; pRect->bottom = iNewHeight + pRect->top; } else { pRect->bottom = prgSize->cy; // work out the blank space and split it evenly between the left and the right... int iNewWidth = ((iWidth * 1000) / iYRatio); if (iNewWidth == 0) { iNewWidth = 1; } int iRemainder = prgSize->cx - iNewWidth; pRect->left = iRemainder / 2; pRect->right = iNewWidth + pRect->left; } } void CImgCtxThumb::CalculateAspectRatio(const SIZE * prgSize, RECT * pRect) { int iHeight = abs(pRect->bottom - pRect->top); int iWidth = abs(pRect->right - pRect->left); // check if the initial bitmap is larger than the size of the thumbnail. if (iWidth > prgSize->cx || iHeight > prgSize->cy) { pRect->left = 0; pRect->top = 0; pRect->right = iWidth; pRect->bottom = iHeight; CalcAspectScaledRect(prgSize, pRect); } else { // if the bitmap was smaller than the thumbnail, just center it. pRect->left = (prgSize->cx - iWidth) / 2; pRect->top = (prgSize->cy- iHeight) / 2; pRect->right = pRect->left + iWidth; pRect->bottom = pRect->top + iHeight; } } STDMETHODIMP CImgCtxThumb::Extract(HBITMAP * phBmpThumbnail) { m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (!m_hEvent) { return E_OUTOFMEMORY; } m_phBmp = phBmpThumbnail; return InternalResume(); } STDMETHODIMP CImgCtxThumb::GetDateStamp(FILETIME * pftTimeStamp) { HRESULT hr = E_FAIL; ASSERT(pftTimeStamp); WIN32_FIND_DATAW rgData; WCHAR szBuffer[MAX_PATH]; DWORD dwSize = ARRAYSIZE(szBuffer); hr = PathCreateFromUrlW(m_szPath, szBuffer, &dwSize, URL_UNESCAPE); if (SUCCEEDED(hr)) { HANDLE hFind = FindFirstFileW(szBuffer, &rgData); if (INVALID_HANDLE_VALUE != hFind) { *pftTimeStamp = rgData.ftLastWriteTime; FindClose(hFind); } else { hr = E_FAIL; } } return hr; } STDMETHODIMP CImgCtxThumb::GetClassID(CLSID *pClassID) { return E_NOTIMPL; } STDMETHODIMP CImgCtxThumb::IsDirty() { return E_NOTIMPL; } STDMETHODIMP CImgCtxThumb::Load(LPCOLESTR pszFileName, DWORD dwMode) { if (!pszFileName) { return E_INVALIDARG; } if (lstrlenW(pszFileName) > ARRAYSIZE(m_szPath) - 6) { return E_FAIL; } DWORD dwSize = ARRAYSIZE(m_szPath); return UrlCreateFromPathW(pszFileName, m_szPath, &dwSize, URL_ESCAPE_UNSAFE); } STDMETHODIMP CImgCtxThumb::Save(LPCOLESTR pszFileName, BOOL fRemember) { return E_NOTIMPL; } STDMETHODIMP CImgCtxThumb::SaveCompleted(LPCOLESTR pszFileName) { return E_NOTIMPL; } STDMETHODIMP CImgCtxThumb::GetCurFile(LPOLESTR *ppszFileName) { return E_NOTIMPL; } STDMETHODIMP CImgCtxThumb::Run() { return E_NOTIMPL; } STDMETHODIMP CImgCtxThumb::Kill(BOOL fUnused) { LONG lRes = InterlockedExchange(& m_lState, IRTIR_TASK_PENDING); if (lRes != IRTIR_TASK_RUNNING) { m_lState = lRes; } if (m_hEvent) SetEvent(m_hEvent); return S_OK; } STDMETHODIMP CImgCtxThumb::Resume() { if (m_lState != IRTIR_TASK_SUSPENDED) { return S_FALSE; } return InternalResume(); } STDMETHODIMP CImgCtxThumb::Suspend() { LONG lRes = InterlockedExchange(& m_lState, IRTIR_TASK_SUSPENDED); if (lRes != IRTIR_TASK_RUNNING) { m_lState = lRes; } if (m_hEvent) SetEvent(m_hEvent); return S_OK; } STDMETHODIMP_(ULONG) CImgCtxThumb::IsRunning() { return m_lState; } STDMETHODIMP CImgCtxThumb::InternalResume() { if (m_phBmp == NULL) { return E_UNEXPECTED; } m_lState = IRTIR_TASK_RUNNING; HRESULT hr = S_OK; if (!m_pImg) { hr = CoCreateInstance(CLSID_IImgCtx, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IImgCtx, &m_pImg)); if (SUCCEEDED(hr)) { ASSERT(m_pImg); hr = m_pImg->Load(m_szPath, DWN_RAWIMAGE | m_dwRecClrDepth); if (SUCCEEDED(hr)) { hr = m_pImg->SetCallback(OnImgCtxChange, this); } if (SUCCEEDED(hr)) { hr = m_pImg->SelectChanges(IMGCHG_COMPLETE, 0, TRUE); } if (FAILED(hr)) { ATOMICRELEASE(m_pImg); m_lState = IRTIR_TASK_FINISHED; return hr; } } else { m_lState = IRTIR_TASK_FINISHED; return hr; } } ULONG fState; SIZE rgSize; m_pImg->GetStateInfo(&fState, &rgSize, TRUE); if (!(fState & IMGLOAD_COMPLETE)) { do { DWORD dwRet = MsgWaitForMultipleObjects(1, &m_hEvent, FALSE, INFINITE, QS_ALLINPUT); if (dwRet != WAIT_OBJECT_0) { // check the event anyway, msgs get checked first, so // it could take a while for this to get fired otherwise.. dwRet = WaitForSingleObject(m_hEvent, 0); } if (dwRet == WAIT_OBJECT_0) { break; } MSG msg; // empty the message queue... while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if ((msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) || (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST && msg.message != WM_MOUSEMOVE)) { continue; } TranslateMessage(&msg); DispatchMessage(&msg); } } while (TRUE); // check why we broke out... if (m_lState == IRTIR_TASK_PENDING) { m_lState = IRTIR_TASK_FINISHED; m_pImg->Disconnect(); ATOMICRELEASE(m_pImg); return E_FAIL; } if (m_lState == IRTIR_TASK_SUSPENDED) return E_PENDING; m_pImg->GetStateInfo(&fState, &rgSize, TRUE); } hr = (fState & IMGLOAD_ERROR) ? E_FAIL : S_OK; if (SUCCEEDED(hr)) { HDC hdc = GetDC(NULL); // LINTASSERT(hdc || !hdc); // 0 semi-ok void *lpBits; HDC hdcBmp = CreateCompatibleDC(hdc); if (hdcBmp && hdc) { struct { BITMAPINFOHEADER bi; DWORD ct[256]; } dib; dib.bi.biSize = sizeof(BITMAPINFOHEADER); // On NT5 we go directly to the thumbnail with StretchBlt // on other OS's we make a full size copy and pass the bits // to ScaleSharpen2(). if (IsOS(OS_WIN2000ORGREATER)) { dib.bi.biWidth = m_rgSize.cx; dib.bi.biHeight = m_rgSize.cy; } else { dib.bi.biWidth = rgSize.cx; dib.bi.biHeight = rgSize.cy; } dib.bi.biPlanes = 1; dib.bi.biBitCount = (WORD) m_dwRecClrDepth; dib.bi.biCompression = BI_RGB; dib.bi.biSizeImage = 0; dib.bi.biXPelsPerMeter = 0; dib.bi.biYPelsPerMeter = 0; dib.bi.biClrUsed = (m_dwRecClrDepth <= 8) ? (1 << m_dwRecClrDepth) : 0; dib.bi.biClrImportant = 0; HPALETTE hpal = NULL; HPALETTE hpalOld = NULL; if (m_dwRecClrDepth <= 8) { if (m_dwRecClrDepth == 8) { // need to get the right palette.... hr = m_pImg->GetPalette(& hpal); } else { hpal = (HPALETTE) GetStockObject(DEFAULT_PALETTE); } if (SUCCEEDED(hr) && hpal) { hpalOld = SelectPalette(hdcBmp, hpal, TRUE); // LINTASSERT(hpalOld || !hpalOld); // 0 semi-ok for SelectPalette RealizePalette(hdcBmp); int n = GetPaletteEntries(hpal, 0, 256, (LPPALETTEENTRY)&dib.ct[0]); ASSERT(n >= (int) dib.bi.biClrUsed); for (int i = 0; i < (int)dib.bi.biClrUsed; i ++) dib.ct[i] = RGB(GetBValue(dib.ct[i]),GetGValue(dib.ct[i]),GetRValue(dib.ct[i])); } } HBITMAP hBmp = CreateDIBSection(hdcBmp, (LPBITMAPINFO)&dib, DIB_RGB_COLORS, &lpBits, NULL, 0); if (hBmp != NULL) { HGDIOBJ hOld = SelectObject(hdcBmp, hBmp); // On NT5 Go directly to the Thumbnail with StretchBlt() if (IsOS(OS_WIN2000ORGREATER)) { // Compute output size of thumbnail RECT rectThumbnail; rectThumbnail.left = 0; rectThumbnail.top = 0; rectThumbnail.right = m_rgSize.cx; rectThumbnail.bottom = m_rgSize.cy; FillRect(hdcBmp, &rectThumbnail, (HBRUSH) (COLOR_WINDOW+1)); rectThumbnail.right = rgSize.cx; rectThumbnail.bottom = rgSize.cy; CalculateAspectRatio (&m_rgSize, &rectThumbnail); // Call DanielC for the StretchBlt SetStretchBltMode (hdcBmp, HALFTONE); // Create the thumbnail m_pImg->StretchBlt(hdcBmp, rectThumbnail.left, rectThumbnail.top, rectThumbnail.right - rectThumbnail.left, rectThumbnail.bottom - rectThumbnail.top, 0, 0, rgSize.cx, rgSize.cy, SRCCOPY); SelectObject(hdcBmp, hOld); *m_phBmp = hBmp; } else { // // On systems other than NT5 make a full size copy of // the bits and pass the copy to ScaleSharpen2(). // RECT rectThumbnail; rectThumbnail.left = 0; rectThumbnail.top = 0; rectThumbnail.right = rgSize.cx; rectThumbnail.bottom = rgSize.cy; FillRect(hdcBmp, &rectThumbnail, (HBRUSH) (COLOR_WINDOW+1)); m_pImg->StretchBlt(hdcBmp, 0, 0, rgSize.cx, rgSize.cy, 0, 0, rgSize.cx, rgSize.cy, SRCCOPY); SelectObject(hdcBmp, hOld); if (m_rgSize.cx == rgSize.cx && m_rgSize.cy == rgSize.cy) { *m_phBmp = hBmp; } else { SIZEL rgCur; rgCur.cx = rgSize.cx; rgCur.cy = rgSize.cy; IScaleAndSharpenImage2 * pScale; hr = CoCreateInstance(CLSID_ThumbnailScaler, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IScaleAndSharpenImage2, &pScale)); if (SUCCEEDED(hr)) { hr = pScale->ScaleSharpen2((BITMAPINFO *) &dib, lpBits, m_phBmp, &m_rgSize, m_dwRecClrDepth, hpal, 20, m_fOrigSize); pScale->Release(); } DeleteObject(hBmp); } } } if (SUCCEEDED(hr) && hpal && m_dwRecClrDepth <= 8) { (void) SelectPalette(hdcBmp, hpalOld, TRUE); RealizePalette(hdcBmp); } if (m_dwRecClrDepth < 8) { // we used a stock 16 colour palette DeletePalette(hpal); } } if (hdc) { ReleaseDC(NULL, hdc); } if (hdcBmp) { DeleteDC(hdcBmp); } } m_pImg->Disconnect(); ATOMICRELEASE(m_pImg); m_lState = IRTIR_TASK_FINISHED; return hr; }