#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));
}