1035 lines
30 KiB
C++
1035 lines
30 KiB
C++
#include "precomp.h"
|
|
#include "ocmm.h"
|
|
#include "thumbutil.h"
|
|
|
|
typedef UCHAR BGR3[3];
|
|
|
|
class CThumbnailMaker
|
|
{
|
|
public:
|
|
CThumbnailMaker();
|
|
~CThumbnailMaker();
|
|
|
|
void Scale(BGR3 *pDst, UINT uiDstWidth, int iDstStep, const BGR3 *pSrc, UINT uiSrcWidth, int iSrcStep);
|
|
HRESULT Init(UINT uiDstWidth, UINT uiDstHeight, UINT uiSrcWidth, UINT uiSrcHeight);
|
|
HRESULT AddScanline(UCHAR *pucSrc, UINT uiY);
|
|
HRESULT AddDIB(BITMAPINFO *pBMI);
|
|
HRESULT AddDIBSECTION(BITMAPINFO *pBMI, void *pBits);
|
|
HRESULT GetBITMAPINFO(BITMAPINFO **ppBMInfo, DWORD *pdwSize);
|
|
HRESULT GetSharpenedBITMAPINFO(UINT uiSharpPct, BITMAPINFO **ppBMInfo, DWORD *pdwSize);
|
|
|
|
private:
|
|
UINT _uiDstWidth, _uiDstHeight;
|
|
UINT _uiSrcWidth, _uiSrcHeight;
|
|
BGR3 *_pImH;
|
|
};
|
|
|
|
CThumbnailMaker::CThumbnailMaker()
|
|
{
|
|
_pImH = NULL;
|
|
}
|
|
|
|
CThumbnailMaker::~CThumbnailMaker()
|
|
{
|
|
if (_pImH)
|
|
delete[] _pImH;
|
|
}
|
|
|
|
HRESULT CThumbnailMaker::Init(UINT uiDstWidth, UINT uiDstHeight, UINT uiSrcWidth, UINT uiSrcHeight)
|
|
{
|
|
_uiDstWidth = uiDstWidth;
|
|
_uiDstHeight = uiDstHeight;
|
|
_uiSrcWidth = uiSrcWidth;
|
|
_uiSrcHeight = uiSrcHeight;
|
|
|
|
if (_uiDstWidth < 1 || _uiDstHeight < 1 ||
|
|
_uiSrcWidth < 1 || _uiSrcHeight < 1)
|
|
return E_INVALIDARG;
|
|
|
|
if (_pImH)
|
|
delete[] _pImH;
|
|
|
|
_pImH = new BGR3[_uiDstWidth * _uiSrcHeight];
|
|
if (_pImH == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
void CThumbnailMaker::Scale( BGR3 *pDst, UINT dxDst, int iDstBytStep,
|
|
const BGR3 *pSrc, UINT dxSrc, int iSrcBytStep)
|
|
{
|
|
int mnum = dxSrc;
|
|
int mden = dxDst;
|
|
|
|
// Scaling up, use a triangle filter.
|
|
if (mden >= mnum)
|
|
{
|
|
int frac = 0;
|
|
|
|
// Adjust the slope so that we calculate the fraction of the
|
|
// "next" pixel to use (i.e. should be 0 for the first and
|
|
// last dst pixel).
|
|
--mnum;
|
|
if (--mden == 0)
|
|
mden = 0; // avoid div by 0
|
|
|
|
BGR3 *pSrc1 = (BGR3 *)(((UCHAR *)pSrc) + iSrcBytStep);
|
|
|
|
for (UINT x = 0; x < dxDst; x++)
|
|
{
|
|
if (frac == 0)
|
|
{
|
|
(*pDst)[0] = (*pSrc)[0];
|
|
(*pDst)[1] = (*pSrc)[1];
|
|
(*pDst)[2] = (*pSrc)[2];
|
|
}
|
|
else
|
|
{
|
|
(*pDst)[0] = ((mden - frac) * (*pSrc)[0] + frac * (*pSrc1)[0]) / mden;
|
|
(*pDst)[1] = ((mden - frac) * (*pSrc)[1] + frac * (*pSrc1)[1]) / mden;
|
|
(*pDst)[2] = ((mden - frac) * (*pSrc)[2] + frac * (*pSrc1)[2]) / mden;
|
|
}
|
|
|
|
pDst = (BGR3 *)((UCHAR *)pDst + iDstBytStep);
|
|
|
|
frac += mnum;
|
|
if (frac >= mden)
|
|
{
|
|
frac -= mden;
|
|
pSrc = (BGR3 *)((UCHAR *)pSrc + iSrcBytStep);
|
|
pSrc1 = (BGR3 *)((UCHAR *)pSrc1 + iSrcBytStep);
|
|
}
|
|
}
|
|
}
|
|
// Scaling down, use a box filter.
|
|
else
|
|
{
|
|
int frac = 0;
|
|
|
|
for (UINT x = 0; x < dxDst; x++)
|
|
{
|
|
UINT uiSum[3] = {0, 0, 0};
|
|
UINT uiCnt = 0;
|
|
|
|
frac += mnum;
|
|
while (frac >= mden)
|
|
{
|
|
uiSum[0] += (*pSrc)[0];
|
|
uiSum[1] += (*pSrc)[1];
|
|
uiSum[2] += (*pSrc)[2];
|
|
uiCnt++;
|
|
|
|
frac -= mden;
|
|
pSrc = (BGR3 *)((UCHAR *)pSrc + iSrcBytStep);
|
|
}
|
|
|
|
(*pDst)[0] = uiSum[0] / uiCnt;
|
|
(*pDst)[1] = uiSum[1] / uiCnt;
|
|
(*pDst)[2] = uiSum[2] / uiCnt;
|
|
|
|
pDst = (BGR3 *)((UCHAR *)pDst + iDstBytStep);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// For AddScanline, we scale the input horizontally into our temporary
|
|
// image buffer.
|
|
//
|
|
HRESULT CThumbnailMaker::AddScanline(UCHAR *pSrc, UINT uiY)
|
|
{
|
|
if (pSrc == NULL || uiY >= _uiSrcHeight)
|
|
return E_INVALIDARG;
|
|
|
|
Scale(_pImH + uiY * _uiDstWidth, _uiDstWidth, sizeof(BGR3), (BGR3 *)pSrc, _uiSrcWidth, sizeof(BGR3));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// For GetBITMAPINFO, we complete the scaling vertically and return the
|
|
// result as a DIB.
|
|
HRESULT CThumbnailMaker::GetBITMAPINFO(BITMAPINFO **ppBMInfo, DWORD *pdwSize)
|
|
{
|
|
*ppBMInfo = NULL;
|
|
|
|
DWORD dwBPL = (((_uiDstWidth * 24) + 31) >> 3) & ~3;
|
|
DWORD dwTotSize = sizeof(BITMAPINFOHEADER) + dwBPL * _uiDstHeight;
|
|
|
|
BITMAPINFO *pBMI = (BITMAPINFO *)CoTaskMemAlloc(dwTotSize);
|
|
if (pBMI == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
BITMAPINFOHEADER *pBMIH = &pBMI->bmiHeader;
|
|
pBMIH->biSize = sizeof(*pBMIH);
|
|
pBMIH->biWidth = _uiDstWidth;
|
|
pBMIH->biHeight = _uiDstHeight;
|
|
pBMIH->biPlanes = 1;
|
|
pBMIH->biBitCount = 24;
|
|
pBMIH->biCompression = BI_RGB;
|
|
pBMIH->biXPelsPerMeter = 0;
|
|
pBMIH->biYPelsPerMeter = 0;
|
|
pBMIH->biSizeImage = dwBPL * _uiDstHeight;
|
|
pBMIH->biClrUsed = 0;
|
|
pBMIH->biClrImportant = 0;
|
|
|
|
UCHAR *pDst = (UCHAR *)pBMIH + pBMIH->biSize + (_uiDstHeight - 1) * dwBPL;
|
|
|
|
for (UINT x = 0; x < _uiDstWidth; x++)
|
|
{
|
|
Scale((BGR3 *)pDst + x, _uiDstHeight, -(int)dwBPL,
|
|
_pImH + x, _uiSrcHeight, _uiDstWidth * sizeof(BGR3));
|
|
}
|
|
|
|
*ppBMInfo = pBMI;
|
|
*pdwSize = dwTotSize;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CThumbnailMaker::GetSharpenedBITMAPINFO(UINT uiSharpPct, BITMAPINFO **ppBMInfo, DWORD *pdwSize)
|
|
{
|
|
#define SCALE 10000
|
|
|
|
if (uiSharpPct > 100)
|
|
return E_INVALIDARG;
|
|
|
|
// Get the unsharpened bitmap.
|
|
DWORD dwSize;
|
|
HRESULT hr = GetBITMAPINFO(ppBMInfo, &dwSize);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
*pdwSize = dwSize;
|
|
|
|
// Create a duplicate to serve as the original.
|
|
BITMAPINFO *pBMISrc = (BITMAPINFO *)new UCHAR[dwSize];
|
|
if (pBMISrc == NULL)
|
|
{
|
|
delete *ppBMInfo;
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
memcpy(pBMISrc, *ppBMInfo, dwSize);
|
|
|
|
int bpl = (pBMISrc->bmiHeader.biWidth * 3 + 3) & ~3;
|
|
|
|
//
|
|
// Sharpen inside a 1 pixel border
|
|
//
|
|
UCHAR *pucDst = (UCHAR *)*ppBMInfo + sizeof(BITMAPINFOHEADER);
|
|
UCHAR *pucSrc[3];
|
|
pucSrc[0] = (UCHAR *)pBMISrc + sizeof(BITMAPINFOHEADER);
|
|
pucSrc[1] = pucSrc[0] + bpl;
|
|
pucSrc[2] = pucSrc[1] + bpl;
|
|
|
|
int wdiag = (10355 * uiSharpPct) / 100;
|
|
int wadj = (14645 * uiSharpPct) / 100;
|
|
int wcent = 4 * (wdiag + wadj);
|
|
|
|
for (int y = 1; y < pBMISrc->bmiHeader.biHeight-1; ++y)
|
|
{
|
|
for (int x = 3*(pBMISrc->bmiHeader.biWidth-2); x >= 3; --x)
|
|
{
|
|
int v = pucDst[x] +
|
|
(pucSrc[1][x] * wcent -
|
|
((pucSrc[0][x - 3] +
|
|
pucSrc[0][x + 3] +
|
|
pucSrc[2][x - 3] +
|
|
pucSrc[2][x + 3]) * wdiag +
|
|
(pucSrc[0][x] +
|
|
pucSrc[1][x - 3] +
|
|
pucSrc[1][x + 3] +
|
|
pucSrc[2][x]) * wadj)) / SCALE;
|
|
|
|
pucDst[x] = v < 0 ? 0 : v > 255 ? 255 : v;
|
|
}
|
|
|
|
pucDst += bpl;
|
|
pucSrc[0] = pucSrc[1];
|
|
pucSrc[1] = pucSrc[2];
|
|
pucSrc[2] += bpl;
|
|
}
|
|
|
|
delete[] pBMISrc;
|
|
|
|
return S_OK;
|
|
#undef SCALE
|
|
}
|
|
|
|
HRESULT ThumbnailMaker_Create(CThumbnailMaker **ppThumbMaker)
|
|
{
|
|
*ppThumbMaker = new CThumbnailMaker;
|
|
return *ppThumbMaker ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
|
|
HRESULT CThumbnailMaker::AddDIB(BITMAPINFO *pBMI)
|
|
{
|
|
int ncolors = pBMI->bmiHeader.biClrUsed;
|
|
if (ncolors == 0 && pBMI->bmiHeader.biBitCount <= 8)
|
|
ncolors = 1 << pBMI->bmiHeader.biBitCount;
|
|
|
|
if (pBMI->bmiHeader.biBitCount == 16 ||
|
|
pBMI->bmiHeader.biBitCount == 32)
|
|
{
|
|
if (pBMI->bmiHeader.biCompression == BI_BITFIELDS)
|
|
{
|
|
ncolors = 3;
|
|
}
|
|
}
|
|
|
|
UCHAR *pBits = (UCHAR *)&pBMI->bmiColors[0] + ncolors * sizeof(RGBQUAD);
|
|
|
|
return AddDIBSECTION(pBMI, (void *) pBits);
|
|
}
|
|
|
|
HRESULT CThumbnailMaker::AddDIBSECTION(BITMAPINFO *pBMI, void *pBits)
|
|
{
|
|
RGBQUAD *pRGBQ, *pQ;
|
|
UCHAR *pucBits0, *pucBits, *pB, *pucBits240, *pucBits24, *pB24;
|
|
int bpl;
|
|
int x, y, ncolors;
|
|
ULONG rmask, gmask, bmask;
|
|
int rshift, gshift, bshift;
|
|
HRESULT hr;
|
|
|
|
//
|
|
// Make sure that thumbnail maker has been properly initialized.
|
|
//
|
|
if (pBMI == NULL)
|
|
return E_INVALIDARG;
|
|
|
|
if (pBMI->bmiHeader.biWidth != (LONG)_uiSrcWidth ||
|
|
pBMI->bmiHeader.biHeight != (LONG)_uiSrcHeight)
|
|
return E_INVALIDARG;
|
|
|
|
//
|
|
// Don't handle RLE.
|
|
//
|
|
if (pBMI->bmiHeader.biCompression != BI_RGB &&
|
|
pBMI->bmiHeader.biCompression != BI_BITFIELDS)
|
|
return E_INVALIDARG;
|
|
|
|
pRGBQ = (RGBQUAD *)&pBMI->bmiColors[0];
|
|
|
|
ncolors = pBMI->bmiHeader.biClrUsed;
|
|
if (ncolors == 0 && pBMI->bmiHeader.biBitCount <= 8)
|
|
ncolors = 1 << pBMI->bmiHeader.biBitCount;
|
|
|
|
//
|
|
// Decode 16/32bpp with masks.
|
|
//
|
|
if (pBMI->bmiHeader.biBitCount == 16 ||
|
|
pBMI->bmiHeader.biBitCount == 32)
|
|
{
|
|
if (pBMI->bmiHeader.biCompression == BI_BITFIELDS)
|
|
{
|
|
rmask = ((ULONG *)pRGBQ)[0];
|
|
gmask = ((ULONG *)pRGBQ)[1];
|
|
bmask = ((ULONG *)pRGBQ)[2];
|
|
ncolors = 3;
|
|
}
|
|
else if (pBMI->bmiHeader.biBitCount == 16)
|
|
{
|
|
rmask = 0x7c00;
|
|
gmask = 0x03e0;
|
|
bmask = 0x001f;
|
|
}
|
|
else /* 32 */
|
|
{
|
|
rmask = 0xff0000;
|
|
gmask = 0x00ff00;
|
|
bmask = 0x0000ff;
|
|
}
|
|
|
|
for (rshift = 0; (rmask & 1) == 0; rmask >>= 1, ++rshift);
|
|
if (rmask == 0)
|
|
rmask = 1;
|
|
for (gshift = 0; (gmask & 1) == 0; gmask >>= 1, ++gshift);
|
|
if (gmask == 0)
|
|
gmask = 1;
|
|
for (bshift = 0; (bmask & 1) == 0; bmask >>= 1, ++bshift);
|
|
if (bmask == 0)
|
|
bmask = 1;
|
|
}
|
|
|
|
bpl = ((pBMI->bmiHeader.biBitCount * _uiSrcWidth + 31) >> 3) & ~3;
|
|
|
|
pucBits0 = (UCHAR *) pBits;
|
|
pucBits = pucBits0;
|
|
|
|
if (pBMI->bmiHeader.biBitCount == 24)
|
|
pucBits240 = pucBits;
|
|
else
|
|
{
|
|
int bpl24 = (_uiSrcWidth * 3 + 3) & ~3;
|
|
|
|
pucBits240 = new UCHAR[bpl24];
|
|
if (pucBits240 == NULL)
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
pucBits24 = pucBits240;
|
|
|
|
hr = S_OK;
|
|
|
|
for (y = 0; y < (int)_uiSrcHeight; ++y)
|
|
{
|
|
pB = pucBits;
|
|
pB24 = pucBits24;
|
|
|
|
switch (pBMI->bmiHeader.biBitCount)
|
|
{
|
|
case 1:
|
|
for (x = _uiSrcWidth; x >= 8; x -= 8)
|
|
{
|
|
pQ = &pRGBQ[(*pB >> 7) & 1];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
|
|
pQ = &pRGBQ[(*pB >> 6) & 1];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
|
|
pQ = &pRGBQ[(*pB >> 5) & 1];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
|
|
pQ = &pRGBQ[(*pB >> 4) & 1];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
|
|
pQ = &pRGBQ[(*pB >> 3) & 1];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
|
|
pQ = &pRGBQ[(*pB >> 2) & 1];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
|
|
pQ = &pRGBQ[(*pB >> 1) & 1];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
|
|
pQ = &pRGBQ[(*pB++) & 1];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
}
|
|
|
|
if (x > 0)
|
|
{
|
|
int shf = 8;
|
|
|
|
do
|
|
{
|
|
pQ = &pRGBQ[(*pB >> --shf) & 1];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
}
|
|
while (--x);
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
for (x = _uiSrcWidth; x >= 2; x -= 2)
|
|
{
|
|
pQ = &pRGBQ[(*pB >> 4) & 0xf];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
|
|
pQ = &pRGBQ[*pB++ & 0xf];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
}
|
|
|
|
if (x > 0)
|
|
{
|
|
pQ = &pRGBQ[(*pB >> 4) & 0xf];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
|
|
if (x > 1)
|
|
{
|
|
pQ = &pRGBQ[*pB & 0xf];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case 8:
|
|
for (x = _uiSrcWidth; x--;)
|
|
{
|
|
pQ = &pRGBQ[*pB++];
|
|
*pB24++ = pQ->rgbBlue;
|
|
*pB24++ = pQ->rgbGreen;
|
|
*pB24++ = pQ->rgbRed;
|
|
}
|
|
|
|
break;
|
|
|
|
case 16:
|
|
{
|
|
USHORT *pW = (USHORT *)pucBits;
|
|
|
|
for (x = _uiSrcWidth; x--;)
|
|
{
|
|
ULONG w = *pW++;
|
|
|
|
*pB24++ = (UCHAR)
|
|
((((w >> bshift) & bmask) * 255) / bmask);
|
|
*pB24++ = (UCHAR)
|
|
((((w >> gshift) & gmask) * 255) / gmask);
|
|
*pB24++ = (UCHAR)
|
|
((((w >> rshift) & rmask) * 255) / rmask);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 24:
|
|
pucBits24 = pucBits;
|
|
break;
|
|
|
|
case 32:
|
|
{
|
|
ULONG *pD;
|
|
|
|
pD = (ULONG *)pucBits;
|
|
|
|
for (x = _uiSrcWidth; x--;)
|
|
{
|
|
ULONG d = *pD++;
|
|
|
|
*pB24++ = (UCHAR)
|
|
((((d >> bshift) & bmask) * 255) / bmask);
|
|
*pB24++ = (UCHAR)
|
|
((((d >> gshift) & gmask) * 255) / gmask);
|
|
*pB24++ = (UCHAR)
|
|
((((d >> rshift) & rmask) * 255) / rmask);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
delete[] pucBits24;
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
hr = AddScanline(pucBits24, (_uiSrcHeight-1) - y);
|
|
if (FAILED(hr))
|
|
break;
|
|
|
|
pucBits += bpl;
|
|
}
|
|
|
|
if (pucBits240 != pucBits0)
|
|
delete[] pucBits240;
|
|
|
|
return hr;
|
|
}
|
|
|
|
UINT CalcImageSize(const SIZE *prgSize, DWORD dwClrDepth)
|
|
{
|
|
UINT uSize = prgSize->cx * dwClrDepth;
|
|
|
|
uSize *= (prgSize->cy < 0) ? (- prgSize->cy) : prgSize->cy;
|
|
// divide by 8
|
|
UINT uRetVal = uSize >> 3;
|
|
|
|
if (uSize & 7)
|
|
{
|
|
uRetVal++;
|
|
}
|
|
|
|
return uRetVal;
|
|
}
|
|
|
|
// exported as helper for thumbnail implementations (used to come from thumbvw.dll)
|
|
//
|
|
// this code also currently lives in shell32. that should be converted to
|
|
// import these APIs (or expose via COM object)
|
|
|
|
STDAPI_(BOOL) ConvertDIBSECTIONToThumbnail(BITMAPINFO *pbi, void *pBits,
|
|
HBITMAP *phBmpThumbnail, const SIZE *prgSize,
|
|
DWORD dwRecClrDepth, HPALETTE hpal,
|
|
UINT uiSharpPct, BOOL fOrigSize)
|
|
{
|
|
BITMAPINFO *pbiScaled = pbi, *pbiUsed = pbi;
|
|
BITMAPINFOHEADER *pbih = (BITMAPINFOHEADER *)pbi;
|
|
BOOL bRetVal = FALSE, bInverted = FALSE;
|
|
RECT rect;
|
|
HRESULT hr;
|
|
void *pScaledBits = pBits;
|
|
|
|
// the scaling code doesn't handle inverted bitmaps, so we treat
|
|
// them as if they were normal, by inverting the height here and
|
|
// then setting it back before doing a paint.
|
|
if (pbi->bmiHeader.biHeight < 0)
|
|
{
|
|
pbi->bmiHeader.biHeight *= -1;
|
|
bInverted = TRUE;
|
|
}
|
|
|
|
rect.left = 0;
|
|
rect.top = 0;
|
|
rect.right = pbih->biWidth;
|
|
rect.bottom = pbih->biHeight;
|
|
|
|
CalculateAspectRatio(prgSize, &rect);
|
|
|
|
// only bother with the scaling and sharpening if we are messing with the size...
|
|
if ((rect.right - rect.left != pbih->biWidth) || (rect.bottom - rect.top != pbih->biHeight))
|
|
{
|
|
CThumbnailMaker *pThumbMaker;
|
|
hr = ThumbnailMaker_Create(&pThumbMaker);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// initialize thumbnail maker.
|
|
hr = pThumbMaker->Init(rect.right - rect.left, rect.bottom - rect.top,
|
|
pbi->bmiHeader.biWidth, abs(pbi->bmiHeader.biHeight));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// scale image.
|
|
hr = pThumbMaker->AddDIBSECTION(pbiUsed, pBits);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
DWORD dwSize;
|
|
hr = pThumbMaker->GetSharpenedBITMAPINFO(uiSharpPct, &pbiScaled, &dwSize);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
pScaledBits = (LPBYTE)pbiScaled + sizeof(BITMAPINFOHEADER);
|
|
}
|
|
}
|
|
}
|
|
delete pThumbMaker;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// set the height back to negative if that's the way it was before.
|
|
if (bInverted == TRUE)
|
|
pbiScaled->bmiHeader.biHeight *= -1;
|
|
|
|
// now if they have asked for origsize rather than the boxed one, and the colour depth is OK, then
|
|
// return it...
|
|
if (fOrigSize && pbiScaled->bmiHeader.biBitCount <= dwRecClrDepth)
|
|
{
|
|
SIZE rgCreateSize = { pbiScaled->bmiHeader.biWidth, pbiScaled->bmiHeader.biHeight };
|
|
void *pNewBits;
|
|
|
|
// turn the PbiScaled DIB into a HBITMAP...., note we pass the old biInfo so that it can get the palette form
|
|
// it if need be.
|
|
bRetVal = CreateSizedDIBSECTION(&rgCreateSize, pbiScaled->bmiHeader.biBitCount, NULL, pbiScaled, phBmpThumbnail, NULL, &pNewBits);
|
|
|
|
if (bRetVal)
|
|
{
|
|
// copy the image data accross...
|
|
CopyMemory(pNewBits, pScaledBits, CalcImageSize(&rgCreateSize, pbiScaled->bmiHeader.biBitCount));
|
|
}
|
|
|
|
return bRetVal;
|
|
}
|
|
|
|
bRetVal = FactorAspectRatio(pbiScaled, pScaledBits, prgSize, rect,
|
|
dwRecClrDepth, hpal, fOrigSize, GetSysColor(COLOR_WINDOW), phBmpThumbnail);
|
|
|
|
if (pbiScaled != pbi)
|
|
{
|
|
// free the allocated image...
|
|
CoTaskMemFree(pbiScaled);
|
|
}
|
|
|
|
return bRetVal;
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// WINDOWS RAID 135065 (toddb): Use of MulDiv should simplify this code
|
|
//
|
|
void 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 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;
|
|
}
|
|
}
|
|
|
|
LPBYTE g_pbCMAP = NULL;
|
|
|
|
STDAPI_(BOOL) FactorAspectRatio(BITMAPINFO *pbiScaled, void *pScaledBits,
|
|
const SIZE *prgSize, RECT rect, DWORD dwClrDepth,
|
|
HPALETTE hpal, BOOL fOrigSize, COLORREF clrBk, HBITMAP *phBmpThumbnail)
|
|
{
|
|
HDC hdc = CreateCompatibleDC(NULL);
|
|
BITMAPINFOHEADER *pbih = (BITMAPINFOHEADER *)pbiScaled;
|
|
BOOL bRetVal = FALSE;
|
|
int iRetVal = GDI_ERROR;
|
|
BITMAPINFO * pDitheredInfo = NULL;
|
|
void * pDitheredBits = NULL;
|
|
HBITMAP hbmpDithered = NULL;
|
|
|
|
if (hdc)
|
|
{
|
|
if (dwClrDepth == 8)
|
|
{
|
|
RGBQUAD *pSrcColors = NULL;
|
|
LONG nSrcPitch = pbiScaled->bmiHeader.biWidth;
|
|
|
|
// we are going to 8 bits per pixel, we had better dither everything
|
|
// to the same palette.
|
|
GUID guidType = CLSID_NULL;
|
|
switch(pbiScaled->bmiHeader.biBitCount)
|
|
{
|
|
case 32:
|
|
guidType = BFID_RGB_32;
|
|
nSrcPitch *= sizeof(DWORD);
|
|
break;
|
|
|
|
case 24:
|
|
guidType = BFID_RGB_24;
|
|
nSrcPitch *= 3;
|
|
break;
|
|
|
|
case 16:
|
|
// default is 555
|
|
guidType = BFID_RGB_555;
|
|
|
|
// 5-6-5 bitfields has the second DWORD (the green component) as 0x7e00
|
|
if (pbiScaled->bmiHeader.biCompression == BI_BITFIELDS &&
|
|
pbiScaled->bmiColors[1].rgbGreen == 0x7E)
|
|
{
|
|
guidType = BFID_RGB_565;
|
|
}
|
|
nSrcPitch *= sizeof(WORD);
|
|
break;
|
|
|
|
case 8:
|
|
guidType = BFID_RGB_8;
|
|
pSrcColors = pbiScaled->bmiColors;
|
|
|
|
// nSrcPitch is already in bytes...
|
|
break;
|
|
};
|
|
|
|
if (nSrcPitch % 4)
|
|
{
|
|
// round up to the nearest DWORD...
|
|
nSrcPitch = nSrcPitch + 4 - (nSrcPitch %4);
|
|
}
|
|
|
|
// we are going to 8bpp
|
|
LONG nDestPitch = pbiScaled->bmiHeader.biWidth;
|
|
if (nDestPitch % 4)
|
|
{
|
|
// round up to the nearest DWORD...
|
|
nDestPitch = nDestPitch + 4 - (nDestPitch % 4);
|
|
}
|
|
|
|
if (guidType != CLSID_NULL)
|
|
{
|
|
if (g_pbCMAP == NULL)
|
|
{
|
|
// we are always going to the shell halftone palette right now, otherwise
|
|
// computing this inverse colour map consumes a lot of time (approx 2 seconds on
|
|
// a p200)
|
|
if (FAILED(SHGetInverseCMAP((BYTE *)&g_pbCMAP, sizeof(g_pbCMAP))))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
SIZE rgDithered = {pbiScaled->bmiHeader.biWidth, pbiScaled->bmiHeader.biHeight};
|
|
if (rgDithered.cy < 0)
|
|
{
|
|
// invert it
|
|
rgDithered.cy = -rgDithered.cy;
|
|
}
|
|
|
|
if (CreateSizedDIBSECTION(&rgDithered, dwClrDepth, hpal, NULL, &hbmpDithered, &pDitheredInfo, &pDitheredBits))
|
|
{
|
|
ASSERT(pDitheredInfo && pDitheredBits);
|
|
|
|
// dither....
|
|
IIntDitherer *pDither;
|
|
HRESULT hr = CoCreateInstance(CLSID_IntDitherer, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_PPV_ARG(IIntDitherer, &pDither));
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pDither->DitherTo8bpp((LPBYTE) pDitheredBits, nDestPitch,
|
|
(LPBYTE) pScaledBits, nSrcPitch, guidType,
|
|
pDitheredInfo->bmiColors, pSrcColors,
|
|
g_pbCMAP, 0, 0, rgDithered.cx, rgDithered.cy,
|
|
-1, -1);
|
|
|
|
pDither->Release();
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// if the height was inverted, then invert it in the destination bitmap
|
|
if (rgDithered.cy != pbiScaled->bmiHeader.biHeight)
|
|
{
|
|
pDitheredInfo->bmiHeader.biHeight = - rgDithered.cy;
|
|
}
|
|
|
|
// switch to the new image .....
|
|
pbiScaled = pDitheredInfo;
|
|
pScaledBits = pDitheredBits;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// create thumbnail bitmap and copy image into it.
|
|
if (CreateSizedDIBSECTION(prgSize, dwClrDepth, hpal, NULL, phBmpThumbnail, NULL, NULL))
|
|
{
|
|
HBITMAP hBmpOld = (HBITMAP) SelectObject(hdc, *phBmpThumbnail);
|
|
|
|
SetStretchBltMode(hdc, COLORONCOLOR);
|
|
|
|
HGDIOBJ hBrush = CreateSolidBrush(clrBk);
|
|
HGDIOBJ hPen = GetStockObject(WHITE_PEN);
|
|
|
|
HGDIOBJ hOldBrush = SelectObject(hdc, hBrush);
|
|
HGDIOBJ hOldPen = SelectObject(hdc, hPen);
|
|
|
|
HPALETTE hpalOld;
|
|
if (hpal)
|
|
{
|
|
hpalOld = SelectPalette(hdc, hpal, TRUE);
|
|
RealizePalette(hdc);
|
|
}
|
|
|
|
SetMapMode(hdc, MM_TEXT);
|
|
|
|
Rectangle(hdc, 0, 0, prgSize->cx, prgSize->cy);
|
|
|
|
int iDstHt = rect.bottom - rect.top;
|
|
int iDstTop = rect.top, iSrcTop = 0;
|
|
if (pbih->biHeight < 0)
|
|
{
|
|
iDstHt *= -1;
|
|
iDstTop = rect.bottom;
|
|
iSrcTop = abs(pbih->biHeight);
|
|
}
|
|
|
|
iRetVal = StretchDIBits(hdc, rect.left, iDstTop, rect.right - rect.left, iDstHt,
|
|
0, iSrcTop, pbih->biWidth, pbih->biHeight,
|
|
pScaledBits, pbiScaled, DIB_RGB_COLORS, SRCCOPY);
|
|
|
|
SelectObject(hdc, hOldBrush);
|
|
DeleteObject(hBrush);
|
|
SelectObject(hdc, hOldPen);
|
|
if (hpal)
|
|
{
|
|
SelectPalette(hdc, hpalOld, TRUE);
|
|
RealizePalette(hdc);
|
|
}
|
|
|
|
SelectObject(hdc, hBmpOld);
|
|
}
|
|
|
|
DeleteDC(hdc);
|
|
}
|
|
|
|
if (hbmpDithered)
|
|
{
|
|
DeleteObject(hbmpDithered);
|
|
}
|
|
if (pDitheredInfo)
|
|
{
|
|
LocalFree(pDitheredInfo);
|
|
}
|
|
|
|
return (iRetVal != GDI_ERROR);
|
|
}
|
|
|
|
|
|
STDAPI_(BOOL) CreateSizedDIBSECTION(const SIZE *prgSize, DWORD dwClrDepth, HPALETTE hpal,
|
|
const BITMAPINFO *pCurInfo, HBITMAP *phBmp, BITMAPINFO **ppBMI, void **ppBits)
|
|
{
|
|
*phBmp = NULL;
|
|
|
|
HDC hdc = GetDC(NULL);
|
|
if (hdc)
|
|
{
|
|
HDC hdcBmp = CreateCompatibleDC(hdc);
|
|
if (hdcBmp)
|
|
{
|
|
struct {
|
|
BITMAPINFOHEADER bi;
|
|
DWORD ct[256];
|
|
} dib;
|
|
|
|
dib.bi.biSize = sizeof(dib.bi);
|
|
dib.bi.biWidth = prgSize->cx;
|
|
dib.bi.biHeight = prgSize->cy;
|
|
dib.bi.biPlanes = 1;
|
|
dib.bi.biBitCount = (WORD) dwClrDepth;
|
|
dib.bi.biCompression = BI_RGB;
|
|
dib.bi.biSizeImage = CalcImageSize(prgSize, dwClrDepth);
|
|
dib.bi.biXPelsPerMeter = 0;
|
|
dib.bi.biYPelsPerMeter = 0;
|
|
dib.bi.biClrUsed = (dwClrDepth <= 8) ? (1 << dwClrDepth) : 0;
|
|
dib.bi.biClrImportant = 0;
|
|
|
|
HPALETTE hpalOld = NULL;
|
|
|
|
if (dwClrDepth <= 8)
|
|
{
|
|
// if they passed us the old structure with colour info, and we are the same bit depth, then copy it...
|
|
if (pCurInfo && pCurInfo->bmiHeader.biBitCount == dwClrDepth)
|
|
{
|
|
// use the passed in colour info to generate the DIBSECTION
|
|
int iColours = pCurInfo->bmiHeader.biClrUsed;
|
|
|
|
if (!iColours)
|
|
{
|
|
iColours = dib.bi.biClrUsed;
|
|
}
|
|
|
|
// copy the data accross...
|
|
CopyMemory(dib.ct, pCurInfo->bmiColors, sizeof(RGBQUAD) * iColours);
|
|
}
|
|
else
|
|
{
|
|
// need to get the right palette....
|
|
hpalOld = SelectPalette(hdcBmp, hpal, TRUE);
|
|
RealizePalette(hdcBmp);
|
|
|
|
int n = GetPaletteEntries(hpal, 0, 256, (LPPALETTEENTRY)&dib.ct[0]);
|
|
|
|
ASSERT(n >= (int) dib.bi.biClrUsed);
|
|
|
|
// now convert the PALETTEENTRY to RGBQUAD
|
|
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]));
|
|
}
|
|
}
|
|
}
|
|
|
|
void *pbits;
|
|
*phBmp = CreateDIBSection(hdcBmp, (LPBITMAPINFO)&dib, DIB_RGB_COLORS, &pbits, NULL, 0);
|
|
if (*phBmp)
|
|
{
|
|
if (ppBMI)
|
|
{
|
|
*ppBMI = (BITMAPINFO *)LocalAlloc(LPTR, sizeof(dib));
|
|
if (*ppBMI)
|
|
{
|
|
CopyMemory(*ppBMI, &dib, sizeof(dib));
|
|
}
|
|
}
|
|
if (ppBits)
|
|
{
|
|
*ppBits = pbits;
|
|
}
|
|
}
|
|
DeleteDC(hdcBmp);
|
|
}
|
|
ReleaseDC(NULL, hdc);
|
|
}
|
|
return (*phBmp != NULL);
|
|
}
|
|
|
|
STDAPI_(void *) CalcBitsOffsetInDIB(BITMAPINFO *pBMI)
|
|
{
|
|
int ncolors = pBMI->bmiHeader.biClrUsed;
|
|
if (ncolors == 0 && pBMI->bmiHeader.biBitCount <= 8)
|
|
ncolors = 1 << pBMI->bmiHeader.biBitCount;
|
|
|
|
if (pBMI->bmiHeader.biBitCount == 16 ||
|
|
pBMI->bmiHeader.biBitCount == 32)
|
|
{
|
|
if (pBMI->bmiHeader.biCompression == BI_BITFIELDS)
|
|
{
|
|
ncolors = 3;
|
|
}
|
|
}
|
|
return (void *)((UCHAR *)&pBMI->bmiColors[0] + ncolors * sizeof(RGBQUAD));
|
|
}
|
|
|
|
|