/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"

#include <tools/svwin.h>
#include <vcl/bitmap.hxx> // for BitmapSystemData
#include <vcl/salbtype.hxx>
#include <win/wincomp.hxx>
#include <win/salgdi.h>
#include <win/saldata.hxx>
#include <win/salbmp.h>
#include <string.h>
#include <vcl/timer.hxx>
#include <comphelper/broadcasthelper.hxx>
#include <map>

#ifndef min
#define min(a,b)	(((a) < (b)) ? (a) : (b))
#endif
#ifndef max
#define max(a,b)	(((a) > (b)) ? (a) : (b))
#endif

#include <GdiPlus.h>

// ------------------------------------------------------------------
// - Inlines -

inline void ImplSetPixel4( const HPBYTE pScanline, long nX, const BYTE cIndex )
{
	BYTE& rByte = pScanline[ nX >> 1 ];

	( nX & 1 ) ? ( rByte &= 0xf0, rByte |= ( cIndex & 0x0f ) ) :
				 ( rByte &= 0x0f, rByte |= ( cIndex << 4 ) );
}

// ------------------------------------------------------------------
// Helper class to manage Gdiplus::Bitmap instances inside of
// WinSalBitmap

struct Comparator
{
    bool operator()(WinSalBitmap* pA, WinSalBitmap* pB) const
    {
        return pA < pB;
    }
};

typedef ::std::map< WinSalBitmap*, sal_uInt32, Comparator > EntryMap;
static const sal_uInt32 nDefaultCycles(60);

class GdiPlusBuffer : protected comphelper::OBaseMutex, public Timer
{
private:
    EntryMap        maEntries;

public:
    GdiPlusBuffer() 
    :   Timer(),
        maEntries()
    {
        SetTimeout(1000);
        Stop();
    }

    ~GdiPlusBuffer()
    {
        Stop();
    }

    void addEntry(WinSalBitmap& rEntry)
    {
        ::osl::MutexGuard aGuard(m_aMutex);
        EntryMap::iterator aFound = maEntries.find(&rEntry);

        if(aFound == maEntries.end())
        {
            if(maEntries.empty())
            {
                Start();
            }

            maEntries[&rEntry] = nDefaultCycles;
        }
    }

    void remEntry(WinSalBitmap& rEntry)
    {
        ::osl::MutexGuard aGuard(m_aMutex);
        EntryMap::iterator aFound = maEntries.find(&rEntry);

        if(aFound != maEntries.end())
        {
            maEntries.erase(aFound);

            if(maEntries.empty())
            {
                Stop();
            }
        }
    }

    void touchEntry(WinSalBitmap& rEntry)
    {
        ::osl::MutexGuard aGuard(m_aMutex);
        EntryMap::iterator aFound = maEntries.find(&rEntry);

        if(aFound != maEntries.end())
        {
            aFound->second = nDefaultCycles;
        }
    }

    // from parent Timer
    virtual void Timeout()
    {
        ::osl::MutexGuard aGuard(m_aMutex);
        EntryMap::iterator aIter(maEntries.begin());

        while(aIter != maEntries.end())
        {
            if(aIter->second)
            {
                aIter->second--;
                aIter++;
            }
            else
            {
                EntryMap::iterator aDelete(aIter);
                WinSalBitmap* pSource = aDelete->first;
                aIter++;
                maEntries.erase(aDelete);

                if(maEntries.empty())
                {
                    Stop();
                }

                // delete at WinSalBitmap after entry is removed; this
                // way it would not hurt to call remEntry from there, too
                if(pSource->maGdiPlusBitmap.get())
                {
                    pSource->maGdiPlusBitmap.reset();
                    pSource->mpAssociatedAlpha = 0;
                }
            }
        }

        if(!maEntries.empty())
        {
            Start();
        }
    }
};

// ------------------------------------------------------------------
// Global instance of GdiPlusBuffer which manages Gdiplus::Bitmap
// instances

static GdiPlusBuffer aGdiPlusBuffer;

// ------------------------------------------------------------------
// - WinSalBitmap -

WinSalBitmap::WinSalBitmap() 
:   maSize(),
    mhDIB(0),
    mhDDB(0),
    maGdiPlusBitmap(),
    mpAssociatedAlpha(0),
    mnBitCount(0)
{
}

// ------------------------------------------------------------------

WinSalBitmap::~WinSalBitmap()
{
	Destroy();
}

// ------------------------------------------------------------------

void WinSalBitmap::Destroy()
{
    if(maGdiPlusBitmap.get())
    {
        aGdiPlusBuffer.remEntry(*this);
    }

    if( mhDIB )
		GlobalFree( mhDIB );
	else if( mhDDB )
		DeleteObject( mhDDB );

	maSize = Size();
	mnBitCount = 0;
}

// ------------------------------------------------------------------

GdiPlusBmpPtr WinSalBitmap::ImplGetGdiPlusBitmap(const WinSalBitmap* pAlphaSource) const 
{ 
    WinSalBitmap* pThat = const_cast< WinSalBitmap* >(this);

    if(maGdiPlusBitmap.get() && pAlphaSource != mpAssociatedAlpha)
    {
        // #122350# if associated alpha with which the GDIPlus was constructed has changed
        // it is necessary to remove it from buffer, reset reference to it and reconstruct
        pThat->maGdiPlusBitmap.reset();
        aGdiPlusBuffer.remEntry(const_cast< WinSalBitmap& >(*this));
    }

    if(maGdiPlusBitmap.get())
    {
        aGdiPlusBuffer.touchEntry(const_cast< WinSalBitmap& >(*this));
    }
    else
    {
        if(maSize.Width() > 0 && maSize.Height() > 0)
        {
            if(pAlphaSource)
            {
                pThat->maGdiPlusBitmap.reset(pThat->ImplCreateGdiPlusBitmap(*pAlphaSource));
                pThat->mpAssociatedAlpha = pAlphaSource;
            }
            else
            {
                pThat->maGdiPlusBitmap.reset(pThat->ImplCreateGdiPlusBitmap());
                pThat->mpAssociatedAlpha = 0;
            }

            if(maGdiPlusBitmap.get())
            {
                aGdiPlusBuffer.addEntry(*pThat);
            }
        }
    }

    return maGdiPlusBitmap; 
}

// ------------------------------------------------------------------

Gdiplus::Bitmap* WinSalBitmap::ImplCreateGdiPlusBitmap()
{
    Gdiplus::Bitmap* pRetval(0);
    WinSalBitmap* pSalRGB = const_cast< WinSalBitmap* >(this);
    WinSalBitmap* pExtraWinSalRGB = 0;

    if(!pSalRGB->ImplGethDIB())
    {
        // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap
        pExtraWinSalRGB = new WinSalBitmap();
        pExtraWinSalRGB->Create(*pSalRGB, pSalRGB->GetBitCount());
        pSalRGB = pExtraWinSalRGB;
    }

    BitmapBuffer* pRGB = pSalRGB->AcquireBuffer(true);
    BitmapBuffer* pExtraRGB = 0;

    if(pRGB && BMP_FORMAT_24BIT_TC_BGR != (pRGB->mnFormat & ~BMP_FORMAT_TOP_DOWN))
    {
        // convert source bitmap to BMP_FORMAT_24BIT_TC_BGR format if not yet in that format
        SalTwoRect aSalTwoRect;

        aSalTwoRect.mnSrcX = aSalTwoRect.mnSrcY = aSalTwoRect.mnDestX = aSalTwoRect.mnDestY = 0;
        aSalTwoRect.mnSrcWidth = aSalTwoRect.mnDestWidth = pRGB->mnWidth;
        aSalTwoRect.mnSrcHeight = aSalTwoRect.mnDestHeight = pRGB->mnHeight;

        pExtraRGB = StretchAndConvert( 
            *pRGB,
            aSalTwoRect,
            BMP_FORMAT_24BIT_TC_BGR,
            0);

        pSalRGB->ReleaseBuffer(pRGB, true);
        pRGB = pExtraRGB;
    }

    if(pRGB 
        && pRGB->mnWidth > 0
        && pRGB->mnHeight > 0
        && BMP_FORMAT_24BIT_TC_BGR == (pRGB->mnFormat & ~BMP_FORMAT_TOP_DOWN))
    {
        const sal_uInt32 nW(pRGB->mnWidth);
        const sal_uInt32 nH(pRGB->mnHeight);

        pRetval = new Gdiplus::Bitmap(nW, nH, PixelFormat24bppRGB);

        if(pRetval)
        {
            sal_uInt8* pSrcRGB(pRGB->mpBits);
            const sal_uInt32 nExtraRGB(pRGB->mnScanlineSize - (nW * 3));
            const bool bTopDown(pRGB->mnFormat & BMP_FORMAT_TOP_DOWN);
            const Gdiplus::Rect aAllRect(0, 0, nW, nH);
            Gdiplus::BitmapData aGdiPlusBitmapData;
            pRetval->LockBits(&aAllRect, Gdiplus::ImageLockModeWrite, PixelFormat24bppRGB, &aGdiPlusBitmapData);

            // copy data to Gdiplus::Bitmap; format is BGR here in both cases, so memcpy is possible
            for(sal_uInt32 y(0); y < nH; y++)
            {
                const sal_uInt32 nYInsert(bTopDown ? y : nH - y - 1);
                sal_uInt8* targetPixels = (sal_uInt8*)aGdiPlusBitmapData.Scan0 + (nYInsert * aGdiPlusBitmapData.Stride);

                memcpy(targetPixels, pSrcRGB, nW * 3);
                pSrcRGB += nW * 3 + nExtraRGB;
            }

            pRetval->UnlockBits(&aGdiPlusBitmapData);
        }
    }

    if(pExtraRGB)
    {
        // #123478# shockingly, BitmapBuffer does not free the memory it is controlling
        // in it's destructor, this *has to be done handish*. Doing it here now
        delete[] pExtraRGB->mpBits;
        delete pExtraRGB;
    }
    else
    {
        pSalRGB->ReleaseBuffer(pRGB, true);
    }

    if(pExtraWinSalRGB)
    {
        delete pExtraWinSalRGB;
    }

    return pRetval;
}

// ------------------------------------------------------------------

Gdiplus::Bitmap* WinSalBitmap::ImplCreateGdiPlusBitmap(const WinSalBitmap& rAlphaSource)
{
    Gdiplus::Bitmap* pRetval(0);
    WinSalBitmap* pSalRGB = const_cast< WinSalBitmap* >(this);
    WinSalBitmap* pExtraWinSalRGB = 0;

    if(!pSalRGB->ImplGethDIB())
    {
        // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap
        pExtraWinSalRGB = new WinSalBitmap();
        pExtraWinSalRGB->Create(*pSalRGB, pSalRGB->GetBitCount());
        pSalRGB = pExtraWinSalRGB;
    }

    BitmapBuffer* pRGB = pSalRGB->AcquireBuffer(true);
    BitmapBuffer* pExtraRGB = 0;

    if(pRGB && BMP_FORMAT_24BIT_TC_BGR != (pRGB->mnFormat & ~BMP_FORMAT_TOP_DOWN))
    {
        // convert source bitmap to BMP_FORMAT_24BIT_TC_BGR format if not yet in that format
        SalTwoRect aSalTwoRect;

        aSalTwoRect.mnSrcX = aSalTwoRect.mnSrcY = aSalTwoRect.mnDestX = aSalTwoRect.mnDestY = 0;
        aSalTwoRect.mnSrcWidth = aSalTwoRect.mnDestWidth = pRGB->mnWidth;
        aSalTwoRect.mnSrcHeight = aSalTwoRect.mnDestHeight = pRGB->mnHeight;

        pExtraRGB = StretchAndConvert( 
            *pRGB,
            aSalTwoRect,
            BMP_FORMAT_24BIT_TC_BGR,
            0);

        pSalRGB->ReleaseBuffer(pRGB, true);
        pRGB = pExtraRGB;
    }

    WinSalBitmap* pSalA = const_cast< WinSalBitmap* >(&rAlphaSource);
    WinSalBitmap* pExtraWinSalA = 0;

    if(!pSalA->ImplGethDIB())
    {
        // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap
        pExtraWinSalA = new WinSalBitmap();
        pExtraWinSalA->Create(*pSalA, pSalA->GetBitCount());
        pSalA = pExtraWinSalA;
    }

    BitmapBuffer* pA = pSalA->AcquireBuffer(true);
    BitmapBuffer* pExtraA = 0;

    if(pA && BMP_FORMAT_8BIT_PAL != (pA->mnFormat & ~BMP_FORMAT_TOP_DOWN))
    {
        // convert alpha bitmap to BMP_FORMAT_8BIT_PAL format if not yet in that format
        SalTwoRect aSalTwoRect;

        aSalTwoRect.mnSrcX = aSalTwoRect.mnSrcY = aSalTwoRect.mnDestX = aSalTwoRect.mnDestY = 0;
        aSalTwoRect.mnSrcWidth = aSalTwoRect.mnDestWidth = pA->mnWidth;
        aSalTwoRect.mnSrcHeight = aSalTwoRect.mnDestHeight = pA->mnHeight;
        const BitmapPalette& rTargetPalette = Bitmap::GetGreyPalette(256);

        pExtraA = StretchAndConvert( 
            *pA,
            aSalTwoRect,
            BMP_FORMAT_8BIT_PAL,
            &rTargetPalette);

        pSalA->ReleaseBuffer(pA, true);
        pA = pExtraA;
    }

    if(pRGB 
        && pA 
        && pRGB->mnWidth > 0
        && pRGB->mnHeight > 0
        && pRGB->mnWidth == pA->mnWidth 
        && pRGB->mnHeight == pA->mnHeight 
        && BMP_FORMAT_24BIT_TC_BGR == (pRGB->mnFormat & ~BMP_FORMAT_TOP_DOWN)
        && BMP_FORMAT_8BIT_PAL == (pA->mnFormat & ~BMP_FORMAT_TOP_DOWN))
    {
        // we have alpha and bitmap in known formats, create GdiPlus Bitmap as 32bit ARGB
        const sal_uInt32 nW(pRGB->mnWidth);
        const sal_uInt32 nH(pRGB->mnHeight);

        pRetval = new Gdiplus::Bitmap(nW, nH, PixelFormat32bppARGB);

        if(pRetval)
        {
            sal_uInt8* pSrcRGB(pRGB->mpBits);
            sal_uInt8* pSrcA(pA->mpBits);
            const sal_uInt32 nExtraRGB(pRGB->mnScanlineSize - (nW * 3));
            const sal_uInt32 nExtraA(pA->mnScanlineSize - nW);
            const bool bTopDown(pRGB->mnFormat & BMP_FORMAT_TOP_DOWN);
            const Gdiplus::Rect aAllRect(0, 0, nW, nH);
            Gdiplus::BitmapData aGdiPlusBitmapData;
            pRetval->LockBits(&aAllRect, Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &aGdiPlusBitmapData);

            // copy data to Gdiplus::Bitmap; format is BGRA; need to mix BGR from Bitmap and
            // A from alpha, so inner loop is needed (who invented BitmapEx..?)
            for(sal_uInt32 y(0); y < nH; y++)
            {
                const sal_uInt32 nYInsert(bTopDown ? y : nH - y - 1);
                sal_uInt8* targetPixels = (sal_uInt8*)aGdiPlusBitmapData.Scan0 + (nYInsert * aGdiPlusBitmapData.Stride);

                for(sal_uInt32 x(0); x < nW; x++)
                {
                    *targetPixels++ = *pSrcRGB++;
                    *targetPixels++ = *pSrcRGB++;
                    *targetPixels++ = *pSrcRGB++;
                    *targetPixels++ = 0xff - *pSrcA++;
                }

                pSrcRGB += nExtraRGB;
                pSrcA += nExtraA;
            }

            pRetval->UnlockBits(&aGdiPlusBitmapData);
        }
    }

    if(pExtraA)
    {
        // #123478# shockingly, BitmapBuffer does not free the memory it is controlling
        // in it's destructor, this *has to be done handish*. Doing it here now
        delete[] pExtraA->mpBits;
        delete pExtraA;
    }
    else
    {
        pSalA->ReleaseBuffer(pA, true);
    }

    if(pExtraWinSalA)
    {
        delete pExtraWinSalA;
    }

    if(pExtraRGB)
    {
        // #123478# shockingly, BitmapBuffer does not free the memory it is controlling
        // in it's destructor, this *has to be done handish*. Doing it here now
        delete[] pExtraRGB->mpBits;
        delete pExtraRGB;
    }
    else
    {
        pSalRGB->ReleaseBuffer(pRGB, true);
    }

    if(pExtraWinSalRGB)
    {
        delete pExtraWinSalRGB;
    }

    return pRetval;
}

// ------------------------------------------------------------------

bool WinSalBitmap::Create( HANDLE hBitmap, bool bDIB, bool bCopyHandle )
{
	bool bRet = TRUE;

	if( bDIB )
		mhDIB = (HGLOBAL) ( bCopyHandle ? ImplCopyDIBOrDDB( hBitmap, TRUE ) : hBitmap );
	else
		mhDDB = (HBITMAP) ( bCopyHandle ? ImplCopyDIBOrDDB( hBitmap, FALSE ) : hBitmap );

	if( mhDIB )
	{
		PBITMAPINFOHEADER pBIH = (PBITMAPINFOHEADER) GlobalLock( mhDIB );

		maSize = Size( pBIH->biWidth, pBIH->biHeight );
		mnBitCount = pBIH->biBitCount;

		if( mnBitCount )
			mnBitCount = ( mnBitCount <= 1 ) ? 1 : ( mnBitCount <= 4 ) ? 4 : ( mnBitCount <= 8 ) ? 8 : 24;

		GlobalUnlock( mhDIB );
	}
	else if( mhDDB )
	{
		BITMAP	aDDBInfo;

		if( WIN_GetObject( mhDDB, sizeof( BITMAP ), &aDDBInfo ) )
		{
			maSize = Size( aDDBInfo.bmWidth, aDDBInfo.bmHeight );
			mnBitCount = aDDBInfo.bmPlanes * aDDBInfo.bmBitsPixel;

			if( mnBitCount )
			{
				mnBitCount = ( mnBitCount <= 1 ) ? 1 :
							 ( mnBitCount <= 4 ) ? 4 :
							 ( mnBitCount <= 8 ) ? 8 : 24;
			}
		}
		else
		{
			mhDDB = 0;
			bRet = FALSE;
		}
	}
	else
		bRet = FALSE;

	return bRet;
}

// ------------------------------------------------------------------

bool WinSalBitmap::Create( const Size& rSize, sal_uInt16 nBitCount, const BitmapPalette& rPal )
{
	bool bRet = FALSE;

	mhDIB = ImplCreateDIB( rSize, nBitCount, rPal );

	if( mhDIB )
	{
		maSize = rSize;
		mnBitCount = nBitCount;
		bRet = TRUE;
	}

	return bRet;
}

// ------------------------------------------------------------------

bool WinSalBitmap::Create( const SalBitmap& rSSalBitmap )
{
	bool bRet = FALSE;
    const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap);

	if ( rSalBitmap.mhDIB || rSalBitmap.mhDDB )
	{
		HANDLE hNewHdl = ImplCopyDIBOrDDB( rSalBitmap.mhDIB ? rSalBitmap.mhDIB : rSalBitmap.mhDDB,
										   rSalBitmap.mhDIB != 0 );

		if ( hNewHdl )
		{
			if( rSalBitmap.mhDIB )
				mhDIB = (HGLOBAL) hNewHdl;
			else if( rSalBitmap.mhDDB )
				mhDDB = (HBITMAP) hNewHdl;

			maSize = rSalBitmap.maSize;
			mnBitCount = rSalBitmap.mnBitCount;

			bRet = TRUE;
		}
	}

	return bRet;
}

// ------------------------------------------------------------------

bool WinSalBitmap::Create( const SalBitmap& rSSalBmp, SalGraphics* pSGraphics )
{
	bool bRet = FALSE;

    const WinSalBitmap& rSalBmp = static_cast<const WinSalBitmap&>(rSSalBmp);
    WinSalGraphics* pGraphics = static_cast<WinSalGraphics*>(pSGraphics);

	if( rSalBmp.mhDIB )
	{
		PBITMAPINFO 		pBI = (PBITMAPINFO) GlobalLock( rSalBmp.mhDIB );
		PBITMAPINFOHEADER	pBIH = (PBITMAPINFOHEADER) pBI;
		HDC 				hDC  = pGraphics->getHDC();
		HBITMAP 			hNewDDB;
		BITMAP				aDDBInfo;
		PBYTE				pBits = (PBYTE) pBI + *(DWORD*) pBI +
							ImplGetDIBColorCount( rSalBmp.mhDIB ) * sizeof( RGBQUAD );

		if( pBIH->biBitCount == 1 )
		{
			hNewDDB = CreateBitmap( pBIH->biWidth, pBIH->biHeight, 1, 1, NULL );

			if( hNewDDB )
				SetDIBits( hDC, hNewDDB, 0, pBIH->biHeight, pBits, pBI, DIB_RGB_COLORS );
		}
		else
			hNewDDB = CreateDIBitmap( hDC, (PBITMAPINFOHEADER) pBI, CBM_INIT, pBits, pBI, DIB_RGB_COLORS );

		GlobalUnlock( rSalBmp.mhDIB );

		if( hNewDDB && WIN_GetObject( hNewDDB, sizeof( BITMAP ), &aDDBInfo ) )
		{
			mhDDB = hNewDDB;
			maSize = Size( aDDBInfo.bmWidth, aDDBInfo.bmHeight );
			mnBitCount = aDDBInfo.bmPlanes * aDDBInfo.bmBitsPixel;

			bRet = TRUE;
		}
		else if( hNewDDB )
			DeleteObject( hNewDDB );
	}

	return bRet;
}

// ------------------------------------------------------------------

bool WinSalBitmap::Create( const SalBitmap& rSSalBmp, sal_uInt16 nNewBitCount )
{
	bool bRet = FALSE;

    const WinSalBitmap& rSalBmp = static_cast<const WinSalBitmap&>(rSSalBmp);

	if( rSalBmp.mhDDB )
	{
		mhDIB = ImplCreateDIB( rSalBmp.maSize, nNewBitCount, BitmapPalette() );

		if( mhDIB )
		{
			PBITMAPINFO pBI = (PBITMAPINFO) GlobalLock( mhDIB );
			const int	nLines = (int) rSalBmp.maSize.Height();
			HDC 		hDC = GetDC( 0 );
			PBYTE		pBits = (PBYTE) pBI + *(DWORD*) pBI +
								ImplGetDIBColorCount( mhDIB ) * sizeof( RGBQUAD );
			SalData*	pSalData = GetSalData();
			HPALETTE	hOldPal = 0;

			if ( pSalData->mhDitherPal )
			{
				hOldPal = SelectPalette( hDC, pSalData->mhDitherPal, TRUE );
				RealizePalette( hDC );
			}

			if( GetDIBits( hDC, rSalBmp.mhDDB, 0, nLines, pBits, pBI, DIB_RGB_COLORS ) == nLines )
			{
				GlobalUnlock( mhDIB );
				maSize = rSalBmp.maSize;
				mnBitCount = nNewBitCount;
				bRet = TRUE;
			}
			else
			{
				GlobalUnlock( mhDIB );
				GlobalFree( mhDIB );
				mhDIB = 0;
			}

			if( hOldPal )
				SelectPalette( hDC, hOldPal, TRUE );

			ReleaseDC( 0, hDC );
		}
	}

	return bRet;
}

// ------------------------------------------------------------------

sal_uInt16 WinSalBitmap::ImplGetDIBColorCount( HGLOBAL hDIB )
{
	sal_uInt16 nColors = 0;

	if( hDIB )
	{
		PBITMAPINFO 		pBI = (PBITMAPINFO) GlobalLock( hDIB );
		PBITMAPINFOHEADER	pBIH = (PBITMAPINFOHEADER) pBI;

		if ( pBIH->biSize != sizeof( BITMAPCOREHEADER ) )
		{
			if( pBIH->biBitCount <= 8 )
			{
				if ( pBIH->biClrUsed )
					nColors = (sal_uInt16) pBIH->biClrUsed;
				else
					nColors = 1 << pBIH->biBitCount;
			}
		}
		else if( ( (PBITMAPCOREHEADER) pBI )->bcBitCount <= 8 )
			nColors = 1 << ( (PBITMAPCOREHEADER) pBI )->bcBitCount;

		GlobalUnlock( hDIB );
	}

	return nColors;
}

// ------------------------------------------------------------------

HGLOBAL WinSalBitmap::ImplCreateDIB( const Size& rSize, sal_uInt16 nBits, const BitmapPalette& rPal )
{
	DBG_ASSERT( nBits == 1 || nBits == 4 || nBits == 8 || nBits == 16 || nBits == 24, "Unsupported BitCount!" );

	HGLOBAL hDIB = 0;

	if( rSize.Width() <= 0 || rSize.Height() <= 0 )
		return hDIB;

	// calculate bitmap size in Bytes
	const sal_uLong nAlignedWidth4Bytes = AlignedWidth4Bytes( nBits * rSize.Width() );
	const sal_uLong nImageSize = nAlignedWidth4Bytes * rSize.Height();
	bool bOverflow = (nImageSize / nAlignedWidth4Bytes) != rSize.Height();
	if( bOverflow )
		return hDIB;

	// allocate bitmap memory including header and palette
	const sal_uInt16 nColors = (nBits <= 8) ? (1 << nBits) : 0;
	const sal_uLong nHeaderSize = sizeof( BITMAPINFOHEADER ) + nColors * sizeof( RGBQUAD );
	bOverflow = (nHeaderSize + nImageSize) < nImageSize;
	if( bOverflow )
		return hDIB;

	hDIB = GlobalAlloc( GHND, nHeaderSize + nImageSize );
	if( !hDIB )
		return hDIB;

	PBITMAPINFO pBI = static_cast<PBITMAPINFO>( GlobalLock( hDIB ) );
	PBITMAPINFOHEADER pBIH = reinterpret_cast<PBITMAPINFOHEADER>( pBI );

	pBIH->biSize = sizeof( BITMAPINFOHEADER );
	pBIH->biWidth = rSize.Width();
	pBIH->biHeight = rSize.Height();
	pBIH->biPlanes = 1;
	pBIH->biBitCount = nBits;
	pBIH->biCompression = BI_RGB;
	pBIH->biSizeImage = nImageSize;
	pBIH->biXPelsPerMeter = 0;
	pBIH->biYPelsPerMeter = 0;
	pBIH->biClrUsed = 0;
	pBIH->biClrImportant = 0;

	if( nColors )
	{
		// copy the palette entries if any
		const sal_uInt16 nMinCount = Min( nColors, rPal.GetEntryCount() );
		if( nMinCount )
			memcpy( pBI->bmiColors, rPal.ImplGetColorBuffer(), nMinCount * sizeof(RGBQUAD) );
	}

	GlobalUnlock( hDIB );

	return hDIB;
}

// ------------------------------------------------------------------

HANDLE WinSalBitmap::ImplCopyDIBOrDDB( HANDLE hHdl, bool bDIB )
{
	HANDLE	hCopy = 0;

	if ( bDIB && hHdl )
	{
		const sal_uLong nSize = GlobalSize( hHdl );

		if ( (hCopy = GlobalAlloc( GHND, nSize  )) != 0 )
		{
			memcpy( (LPSTR) GlobalLock( hCopy ), (LPSTR) GlobalLock( hHdl ), nSize );

			GlobalUnlock( hCopy );
			GlobalUnlock( hHdl );
		}
	}
	else if ( hHdl )
	{
		BITMAP aBmp;

		// Source-Bitmap nach Groesse befragen
		WIN_GetObject( hHdl, sizeof( BITMAP ), (LPSTR) &aBmp );

		// Destination-Bitmap erzeugen
		if ( (hCopy = CreateBitmapIndirect( &aBmp )) != 0 )
		{
			HDC 	hBmpDC = CreateCompatibleDC( 0 );
			HBITMAP hBmpOld = (HBITMAP) SelectObject( hBmpDC, hHdl );
			HDC 	hCopyDC = CreateCompatibleDC( hBmpDC );
			HBITMAP hCopyOld = (HBITMAP) SelectObject( hCopyDC, hCopy );

			BitBlt( hCopyDC, 0, 0, aBmp.bmWidth, aBmp.bmHeight, hBmpDC, 0, 0, SRCCOPY );

			SelectObject( hCopyDC, hCopyOld );
			DeleteDC( hCopyDC );

			SelectObject( hBmpDC, hBmpOld );
			DeleteDC( hBmpDC );
		}
	}

	return hCopy;
}

// ------------------------------------------------------------------

BitmapBuffer* WinSalBitmap::AcquireBuffer( bool /*bReadOnly*/ )
{
	BitmapBuffer* pBuffer = NULL;

	if( mhDIB )
	{
		PBITMAPINFO 		pBI = (PBITMAPINFO) GlobalLock( mhDIB );
		PBITMAPINFOHEADER	pBIH = (PBITMAPINFOHEADER) pBI;

		if( ( pBIH->biCompression == BI_RLE4 ) || ( pBIH->biCompression == BI_RLE8 ) )
		{
			Size	aSizePix( pBIH->biWidth, pBIH->biHeight );
			HGLOBAL hNewDIB = ImplCreateDIB( aSizePix, pBIH->biBitCount, BitmapPalette() );

			if( hNewDIB )
			{
				PBITMAPINFO 		pNewBI = (PBITMAPINFO) GlobalLock( hNewDIB );
				PBITMAPINFOHEADER	pNewBIH = (PBITMAPINFOHEADER) pNewBI;
				const sal_uInt16		nColorCount = ImplGetDIBColorCount( hNewDIB );
				const sal_uLong 		nOffset = *(DWORD*) pBI + nColorCount * sizeof( RGBQUAD );
				BYTE*				pOldBits = (PBYTE) pBI + nOffset;
				BYTE*				pNewBits = (PBYTE) pNewBI + nOffset;

				memcpy( pNewBI, pBI, nOffset );
				pNewBIH->biCompression = 0;
				ImplDecodeRLEBuffer( pOldBits, pNewBits, aSizePix, pBIH->biCompression == BI_RLE4 );

				GlobalUnlock( mhDIB );
				GlobalFree( mhDIB );
				mhDIB = hNewDIB;
				pBI = pNewBI;
				pBIH = pNewBIH;
			}
		}

		if( pBIH->biPlanes == 1 )
		{
			pBuffer = new BitmapBuffer;

			pBuffer->mnFormat = BMP_FORMAT_BOTTOM_UP |
								( pBIH->biBitCount == 1 ? BMP_FORMAT_1BIT_MSB_PAL :
								  pBIH->biBitCount == 4 ? BMP_FORMAT_4BIT_MSN_PAL :
								  pBIH->biBitCount == 8 ? BMP_FORMAT_8BIT_PAL :
								  pBIH->biBitCount == 16 ? BMP_FORMAT_16BIT_TC_LSB_MASK :
								  pBIH->biBitCount == 24 ? BMP_FORMAT_24BIT_TC_BGR :
								  pBIH->biBitCount == 32 ? BMP_FORMAT_32BIT_TC_MASK : 0UL );

			if( BMP_SCANLINE_FORMAT( pBuffer->mnFormat ) )
			{
				pBuffer->mnWidth = maSize.Width();
				pBuffer->mnHeight = maSize.Height();
				pBuffer->mnScanlineSize = AlignedWidth4Bytes( maSize.Width() * pBIH->biBitCount );
				pBuffer->mnBitCount = (sal_uInt16) pBIH->biBitCount;

				if( pBuffer->mnBitCount <= 8 )
				{
					const sal_uInt16 nPalCount = ImplGetDIBColorCount( mhDIB );

					pBuffer->maPalette.SetEntryCount( nPalCount );
					memcpy( pBuffer->maPalette.ImplGetColorBuffer(), pBI->bmiColors, nPalCount * sizeof( RGBQUAD ) );
					pBuffer->mpBits = (PBYTE) pBI + *(DWORD*) pBI + nPalCount * sizeof( RGBQUAD );
				}
				else if( ( pBIH->biBitCount == 16 ) || ( pBIH->biBitCount == 32 ) )
				{
					sal_uLong nOffset = 0UL;

					if( pBIH->biCompression == BI_BITFIELDS )
					{
						nOffset = 3 * sizeof( RGBQUAD );
						pBuffer->maColorMask = ColorMask( *(UINT32*) &pBI->bmiColors[ 0 ],
														  *(UINT32*) &pBI->bmiColors[ 1 ],
														  *(UINT32*) &pBI->bmiColors[ 2 ] );
					}
					else if( pBIH->biBitCount == 16 )
						pBuffer->maColorMask = ColorMask( 0x00007c00UL, 0x000003e0UL, 0x0000001fUL );
					else
						pBuffer->maColorMask = ColorMask( 0x00ff0000UL, 0x0000ff00UL, 0x000000ffUL );

					pBuffer->mpBits = (PBYTE) pBI + *(DWORD*) pBI + nOffset;
				}
				else
					pBuffer->mpBits = (PBYTE) pBI + *(DWORD*) pBI;
			}
			else
			{
				GlobalUnlock( mhDIB );
				delete pBuffer;
				pBuffer = NULL;
			}
		}
		else
			GlobalUnlock( mhDIB );
	}

	return pBuffer;
}

// ------------------------------------------------------------------

void WinSalBitmap::ReleaseBuffer( BitmapBuffer* pBuffer, bool bReadOnly )
{
	if( pBuffer )
	{
		if( mhDIB )
		{
			if( !bReadOnly && !!pBuffer->maPalette )
			{
				PBITMAPINFO 	pBI = (PBITMAPINFO) GlobalLock( mhDIB );
				const sal_uInt16	nCount = pBuffer->maPalette.GetEntryCount();
				const sal_uInt16	nDIBColorCount = ImplGetDIBColorCount( mhDIB );
				memcpy( pBI->bmiColors, pBuffer->maPalette.ImplGetColorBuffer(), Min( nDIBColorCount, nCount ) * sizeof( RGBQUAD ) );
				GlobalUnlock( mhDIB );
			}

			GlobalUnlock( mhDIB );
		}

		delete pBuffer;
	}
}

// ------------------------------------------------------------------

void WinSalBitmap::ImplDecodeRLEBuffer( const BYTE* pSrcBuf, BYTE* pDstBuf,
									 const Size& rSizePixel, bool bRLE4 )
{
	HPBYTE			pRLE = (HPBYTE) pSrcBuf;
	HPBYTE			pDIB = (HPBYTE) pDstBuf;
	HPBYTE			pRow = (HPBYTE) pDstBuf;
	sal_uLong			nWidthAl = AlignedWidth4Bytes( rSizePixel.Width() * ( bRLE4 ? 4UL : 8UL ) );
	HPBYTE			pLast = pDIB + rSizePixel.Height() * nWidthAl - 1;
	sal_uLong			nCountByte;
	sal_uLong			nRunByte;
	sal_uLong			nX = 0;
	sal_uLong			i;
	BYTE			cTmp;
	bool			bEndDecoding = FALSE;

	if( pRLE && pDIB )
	{
		do
		{
			if( ( nCountByte = *pRLE++ ) == 0 )
			{
				nRunByte = *pRLE++;

				if( nRunByte > 2UL )
				{
					if( bRLE4 )
					{
						nCountByte = nRunByte >> 1UL;

						for( i = 0; i < nCountByte; i++ )
						{
							cTmp = *pRLE++;
							ImplSetPixel4( pDIB, nX++, cTmp >> 4 );
							ImplSetPixel4( pDIB, nX++, cTmp & 0x0f );
						}

						if( nRunByte & 1 )
							ImplSetPixel4( pDIB, nX++, *pRLE++ >> 4 );

						if( ( ( nRunByte + 1 ) >> 1 ) & 1 )
							pRLE++;
					}
					else
					{
						memcpy( &pDIB[ nX ], pRLE, nRunByte );
						pRLE += nRunByte;
						nX += nRunByte;

						if( nRunByte & 1 )
							pRLE++;
					}
				}
				else if( !nRunByte )
				{
					pDIB = ( pRow += nWidthAl );
					nX = 0UL;
				}
				else if( nRunByte == 1 )
					bEndDecoding = TRUE;
				else
				{
					nX += *pRLE++;
					pDIB = ( pRow += ( *pRLE++ ) * nWidthAl );
				}
			}
			else
			{
				cTmp = *pRLE++;

				if( bRLE4 )
				{
					nRunByte = nCountByte >> 1;

					for( i = 0; i < nRunByte; i++ )
					{
						ImplSetPixel4( pDIB, nX++, cTmp >> 4 );
						ImplSetPixel4( pDIB, nX++, cTmp & 0x0f );
					}

					if( nCountByte & 1 )
						ImplSetPixel4( pDIB, nX++, cTmp >> 4 );
				}
				else
				{
					for( i = 0; i < nCountByte; i++ )
						pDIB[ nX++ ] = cTmp;
				}
			}
		}
		while( !bEndDecoding && ( pDIB <= pLast ) );
	}
}

bool WinSalBitmap::GetSystemData( BitmapSystemData& rData )
{
    bool bRet = false;
    if( mhDIB || mhDDB )
    {
        bRet = true;
        rData.pDIB = mhDIB;
        rData.pDDB = mhDDB;
        const Size& rSize = GetSize ();
        rData.mnWidth = rSize.Width();
        rData.mnHeight = rSize.Height();
    }
    return bRet;
}
