#include <windows.h>
#include <windowsx.h>
#include <win32.h>
#include "lockbm.h"

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

#ifndef BI_BITFIELDS
    #define BI_BITFIELDS 3
#endif

#ifndef BI_BITMAP
    #define BI_BITMAP   0x4D544942      // 'BITM'
#endif

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

//
//  GDI!GDIInit2()      GDI.403
//
//  this GDI function does the following:
//
//      GetSetBitmapHandle(hbm, 0)  - will return global handle of bitmap
//
//      GetSetBitmapHandle(hbm, h)  - will set global handle to <h>
//
//      GetSetBitmapHandle(hbm, -1) - will set global handle to NULL
//
static HANDLE (FAR PASCAL *GetSetBitmapHandle)(HBITMAP hbm, HANDLE h);

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
#define muldiv(a,b,c) (UINT)(((DWORD)(UINT)(a) * (DWORD)(UINT)(b)) / (UINT)(c))

//////////////////////////////////////////////////////////////////////////////
//
// CanLockBitmaps()
//
// determime if we can lock bitmaps on the current display device
//
//////////////////////////////////////////////////////////////////////////////
BOOL FAR CanLockBitmaps(void)
{
#ifndef _WIN32
    //its not safe to do this on NT. god only knows what gdi.403 is on future
    //nt platforms - the only thing we can be sure of is that it is NOT
    // GetSetBitmapHandle()

    UINT w;
    UINT rc;
    HDC  hdc;
    BOOL f;

    static BOOL fCanLockBitmaps = -1;

    if (fCanLockBitmaps == -1)
    {
        w = (UINT)GetVersion();

        w = ((UINT)LOBYTE(w) << 8) | HIBYTE(w);

        hdc = GetDC(NULL);
        rc = GetDeviceCaps(hdc, RASTERCAPS);
        ReleaseDC(NULL, hdc);

        (FARPROC)GetSetBitmapHandle =
            GetProcAddress(GetModuleHandle(TEXT("GDI")),(LPCSTR)MAKEINTATOM(403));
#ifdef _WIN32
            // MAKEINTATOM returns a LPTSTR.
            // GetProcAddress wants LPCSTR - NOT Unicode.  Hence we cast
#endif

        //
        // assume we dont need this on windows 4.0?
        //
        // what about the DIBENG? it does DEVBITS and in win 4.0?
        //
        // if the display handles device bitmaps, dont do this either
        //

        f = GetProfileIntA("DrawDib", "Bitmaps", TRUE);

#ifdef DEBUG
        fCanLockBitmaps = f && GetSetBitmapHandle != NULL;
#else
        fCanLockBitmaps = f && /* (w < 0x0400) && */
                          !(rc & RC_DEVBITS) &&
                          GetSetBitmapHandle != NULL;
#endif
    }

    return fCanLockBitmaps;
#else
    return FALSE;
#endif
}

//////////////////////////////////////////////////////////////////////////////
//
// LockBitmap
//
// return a pointer to the bitmap bits
//
//////////////////////////////////////////////////////////////////////////////

LPVOID FAR LockBitmap(HBITMAP hbm)
{
    return GetBitmap(hbm, NULL, 0);
}

//////////////////////////////////////////////////////////////////////////////
//
// GetBitmapDIB
//
//////////////////////////////////////////////////////////////////////////////

LPVOID FAR GetBitmapDIB(LPBITMAPINFOHEADER lpbi, LPVOID lpBits, LPVOID p, int cb)
{
    IBITMAP FAR *pbm;

    if (lpBits == NULL)
        lpBits = (LPBYTE)lpbi + (int)lpbi->biSize + lpbi->biClrUsed * sizeof(RGBQUAD);

    if (p == NULL || cb < sizeof(BITMAP))
        return lpBits;

    pbm = p;

    if (lpbi->biCompression == 0)
    {
        switch ((int)lpbi->biBitCount + (int)lpbi->biPlanes*256)
        {
            case 0x0101: pbm->bmType = BM_1BIT;  break;
            case 0x0104: pbm->bmType = BM_4BIT;  break;
            case 0x0108: pbm->bmType = BM_8BIT;  break;
            case 0x0110: pbm->bmType = BM_16555; break;
            case 0x0118: pbm->bmType = BM_24BGR; break;
            case 0x0120: pbm->bmType = BM_32BGR; break;
            case 0x0401: pbm->bmType = BM_VGA;   break;
            default: return NULL;
        }
    }
    else if (lpbi->biCompression == BI_BITFIELDS)
    {
        switch ((int)lpbi->biBitCount + (int)lpbi->biPlanes*256)
        {
            //!!! hack: realy should check the bit fields!
            case 0x0110: pbm->bmType = BM_16565; break;
            case 0x0118: pbm->bmType = BM_24RGB; break;
            case 0x0120: pbm->bmType = BM_32RGB; break;
            default: return NULL;
        }
    }
    else
        return NULL;

    pbm->bmWidth        = (int)lpbi->biWidth;
    pbm->bmHeight       = ((int)lpbi->biHeight > 0) ? (int)lpbi->biHeight : -(int)lpbi->biHeight;
    pbm->bmWidthBytes   = (((int)lpbi->biBitCount * (int)lpbi->biWidth + 31)&~31)/8;
    pbm->bmPlanes       = (BYTE)lpbi->biPlanes;
    pbm->bmBitsPixel    = (BYTE)lpbi->biBitCount;
    pbm->bmBits         = lpBits;

    if (cb > sizeof(BITMAP))
    {
        pbm->bmSegmentIndex = 0;
        pbm->bmScanSegment  = pbm->bmHeight;
        pbm->bmFillBytes    = 0;
        pbm->bmBitmapInfo   = (LONG)(LONG_PTR)lpbi;

        if ((long)lpbi->biHeight < 0)
        {
            pbm->bmNextScan = -pbm->bmWidthBytes;
            pbm->bmOffset   = (long)pbm->bmWidthBytes * (pbm->bmHeight-1);
        }
        else
        {
            pbm->bmNextScan = pbm->bmWidthBytes;
            pbm->bmOffset   = 0;
        }
    }

    return lpBits;
}

#if 0
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

void FAR BitmapXY(IBITMAP FAR *pbm, int x, int y)
{
    UINT t;

    if (pbm->bmFillBytes)
    {
        while (y-- > 0)
        {
            t = (UINT)(pbm->bmOffset & 0xFFFF0000);
            pbm->bmOffset += pbm->bmNextScan;
            if ((UINT)(pbm->bmOffset & 0xFFFF0000) != t)
                pbm->bmOffset += pbm->bmFillBytes;
        }
    }
    else
    {
        pbm->bmOffset += y * (long)pbm->bmNextScan;
    }

    pbm->bmOffset += x * pbm->bmBitsPixel / 8;
}
#endif

//////////////////////////////////////////////////////////////////////////////
//
// GetDIBBitmap
//
//////////////////////////////////////////////////////////////////////////////

/*
 * creates a DIB header for a bitmap, and returns a pointer to the bitmap
 * bits. This pointer is not used - it's checked against NULL, as if this
 * routine returned BOOL.
 *
 * On NT, you can't get the bitmap bits (wrong process) so we return 1 meaning
 * TRUE. You need to call SetBitmapBits to access the bits themselves.
 */
LPVOID FAR GetDIBBitmap(HBITMAP hbm, LPBITMAPINFOHEADER lpbi)
{
    UINT wType;
    BITMAP bm;
    UINT ScansPerSeg;
    UINT FillBytes;

    if (hbm)
        GetObject(hbm, sizeof(bm), &bm);

    wType = GetBitmapType();

    if (wType == 0)
        return NULL;

    lpbi->biSize           = sizeof(BITMAPINFOHEADER);
    lpbi->biWidth          = bm.bmWidth;
    lpbi->biHeight         = bm.bmHeight;
    lpbi->biPlanes         = bm.bmPlanes;
    lpbi->biBitCount       = bm.bmBitsPixel;
    lpbi->biCompression    = 0;
    lpbi->biSizeImage      = (DWORD)(bm.bmWidthBytes * bm.bmPlanes) * (DWORD)bm.bmHeight;
    lpbi->biXPelsPerMeter  = 0;
    lpbi->biYPelsPerMeter  = 0;
    lpbi->biClrUsed        = 0;
    lpbi->biClrImportant   = 0;

    switch(wType & BM_TYPE)
    {
        case BM_VGA:
            break;

        case BM_1BIT:
        case BM_4BIT:
        case BM_8BIT:
            break;

        case BM_16555:
            break;

        case BM_24BGR:
        case BM_32BGR:
            break;

        case BM_16565:
            lpbi->biCompression = BI_BITFIELDS;
            lpbi->biSize += 3 * sizeof(DWORD);
            ((LPDWORD)(lpbi+1))[0] = 0x00F800;
            ((LPDWORD)(lpbi+1))[1] = 0x0007E0;
            ((LPDWORD)(lpbi+1))[2] = 0x00001F;
            break;

        case BM_24RGB:
        case BM_32RGB:
            lpbi->biCompression = BI_BITFIELDS;
            lpbi->biSize += 3 * sizeof(DWORD);
            ((LPDWORD)(lpbi+1))[0] = 0x0000FF;
            ((LPDWORD)(lpbi+1))[1] = 0x00FF00;
            ((LPDWORD)(lpbi+1))[2] = 0xFF0000;
            break;

        default:
            return NULL;
    }

    //
    //  make sure WidthBytes is right, dont forget bitmaps are WORD aligned
    //  and DIBs are DWORD aligned.
    //
    if (bm.bmWidthBytes != ((bm.bmWidth * bm.bmBitsPixel + 31) & ~31)/8)
    {
        if (lpbi->biCompression != 0)
            return NULL;

        lpbi->biCompression = BI_BITMAP;
        lpbi->biXPelsPerMeter  = bm.bmWidthBytes;
    }

    if ((wType & BM_HUGE) && (lpbi->biSizeImage > 64*1024l))
    {
        if (lpbi->biCompression == BI_BITFIELDS)
            return NULL;

        lpbi->biCompression = BI_BITMAP;

        ScansPerSeg = muldiv(64,1024,bm.bmWidthBytes * bm.bmPlanes);
        FillBytes   = (UINT)(64ul*1024 - bm.bmWidthBytes * bm.bmPlanes * ScansPerSeg);

        lpbi->biSizeImage     += FillBytes * (bm.bmHeight / ScansPerSeg);
        lpbi->biXPelsPerMeter  = bm.bmWidthBytes;
        lpbi->biYPelsPerMeter  = FillBytes;
    }

    if (!(wType & BM_BOTTOMTOTOP))
        lpbi->biHeight = -bm.bmHeight;

#ifdef _WIN32
    return (LPVOID) TRUE;
#else
    return LockBitmap(hbm);
#endif
}

//////////////////////////////////////////////////////////////////////////////
//
// GetBitmap
//
//////////////////////////////////////////////////////////////////////////////

LPVOID FAR GetBitmap(HBITMAP hbm, LPVOID p, int cb)
{
    HANDLE h;
    DWORD  dwSize;
    IBITMAP FAR *pbm;
    HDC hdc = NULL;
    HBITMAP hbmT;

    if (!CanLockBitmaps())
        return NULL;

    if (hbm == NULL)
        return NULL;

    h = GetSetBitmapHandle(hbm, 0);

    if (h == NULL)
        return NULL;

    pbm = (LPVOID)GlobalLock(h);

    if (IsBadReadPtr(pbm, sizeof(IBITMAP)))
        return NULL;

    //
    // see if it is realy a bitmap.
    //
    if (pbm->bmType != 0)
        return NULL;

    //
    // make sure the bmBits pointer is valid.
    //
    if (pbm->bmBits == NULL)
    {
        hdc = CreateCompatibleDC(NULL);
        hbmT = SelectObject(hdc, hbm);
    }

    dwSize = (DWORD)pbm->bmHeight * (DWORD)pbm->bmWidthBytes;

    if (IsBadHugeWritePtr((LPVOID)pbm->bmBits, dwSize))
    {
        if (hdc)
        {
            SelectObject(hdc, hbmT);
            DeleteDC(hdc);
        }

        return NULL;
    }

    if (p)
    {
        UINT u;

        hmemcpy(p, pbm, min(cb, sizeof(IBITMAP)));
        pbm = p;

        u = GetBitmapType();

        pbm->bmType = u & BM_TYPE;

        if (cb > sizeof(BITMAP))
        {
            pbm->bmBitmapInfo = 0;
            pbm->bmNextScan = pbm->bmWidthBytes * pbm->bmPlanes;

            if (u & BM_BOTTOMTOTOP)
            {
                pbm->bmOffset = 0;
            }
            else
            {
                pbm->bmOffset   = (long)pbm->bmNextScan * (pbm->bmHeight-1);
                pbm->bmNextScan = -pbm->bmNextScan;
                pbm->bmFillBytes = -pbm->bmFillBytes;
            }

            //
            // see if this particular bitmap is HUGE
            //
            if (!(u & BM_HUGE) || (DWORD)pbm->bmHeight * pbm->bmWidthBytes < 64l*1024)
            {
                pbm->bmFillBytes = 0;
                pbm->bmScanSegment = pbm->bmHeight;
            }
            else
            {
                if (pbm->bmOffset)
                    pbm->bmOffset -= (long)((pbm->bmHeight-1) / pbm->bmScanSegment) * pbm->bmFillBytes;
            }
        }
    }

    if (hdc)
    {
        SelectObject(hdc, hbmT);
        DeleteDC(hdc);
    }

    return (LPVOID)pbm->bmBits;
}

/////////////////////////////////////////////////////////////////////////////
//
//  SetPixel
//
//  some cards cant't seam to do SetPixel right it is amazing they work at all
//
/////////////////////////////////////////////////////////////////////////////

static void SetPixelX(HDC hdc, int x, int y, COLORREF rgb)
{
    RECT rc;

    rc.left = x;
    rc.top  = y;
    rc.right = x+1;
    rc.bottom = y+1;

    SetBkColor(hdc, rgb);
    ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
}

#define SetPixel SetPixelX

///////////////////////////////////////////////////////////////////////////////
//
//  GetSurfaceType
//
///////////////////////////////////////////////////////////////////////////////
#define BCODE _based(_segname("_CODE"))

static BYTE  BCODE bits8[]   = {0x00,0xF9,0xFA,0xFC,0xFF};
static WORD  BCODE bits555[] = {0x0000,0x7C00,0x03E0,0x001F,0x7FFF};
static WORD  BCODE bits5551[]= {0x8000,0xFC00,0x83E0,0x801F,0xFFFF};
static WORD  BCODE bits565[] = {0x0000,0xF800,0x07E0,0x001F,0xFFFF};
static BYTE  BCODE bitsBGR[] = {0x00,0x00,0x00, 0x00,0x00,0xFF, 0x00,0xFF,0x00, 0xFF,0x00,0x00, 0xFF,0xFF,0xFF};
static BYTE  BCODE bitsRGB[] = {0x00,0x00,0x00, 0xFF,0x00,0x00, 0x00,0xFF,0x00, 0x00,0x00,0xFF, 0xFF,0xFF,0xFF};
static DWORD BCODE bitsRGBX[]= {0x000000, 0x0000FF, 0x00FF00, 0xFF0000, 0xFFFFFF};
static DWORD BCODE bitsBGRX[]= {0x000000, 0xFF0000, 0x00FF00, 0x0000FF, 0xFFFFFF};

void FAR TestSurfaceType(HDC hdc, int x, int y)
{
    PatBlt(hdc, x, y, 5, 1, BLACKNESS);

    SetPixel(hdc, x+0, y, RGB(000,000,000));
    SetPixel(hdc, x+1, y, RGB(255,000,000));
    SetPixel(hdc, x+2, y, RGB(000,255,000));
    SetPixel(hdc, x+3, y, RGB(000,000,255));
    SetPixel(hdc, x+4, y, RGB(255,255,255));

    GetPixel(hdc, x, y);
}

UINT FAR GetSurfaceType(LPVOID lpBits)
{
    #define TESTFMT(a,n) \
        if (_fmemcmp(lpBits, (LPVOID)a, sizeof(a)) == 0) return n;

    TESTFMT(bits8,    BM_8BIT);
    TESTFMT(bits555,  BM_16555);
    TESTFMT(bits5551, BM_16555);
    TESTFMT(bits565,  BM_16565);
    TESTFMT(bitsRGB,  BM_24RGB);
    TESTFMT(bitsBGR,  BM_24BGR);
    TESTFMT(bitsRGBX, BM_32RGB);
    TESTFMT(bitsBGRX, BM_32BGR);

    return 0;
}

///////////////////////////////////////////////////////////////////////////////
//
//  GetBitmapType
//
//  return the bitmap type that the display driver uses
//
///////////////////////////////////////////////////////////////////////////////

UINT FAR GetBitmapType()
{
    BITMAP bm;
    HBITMAP hbm;
    HBITMAP hbmT;
    HDC hdc;
    UINT u;
    BYTE bits[20*4*2];

    static UINT wBitmapType = 0xFFFF;

    if (wBitmapType != 0xFFFF)
        return wBitmapType;

    //
    // create a test bitmap (<64k)
    //
    hdc = GetDC(NULL);
    if (hdc == NULL) {
	return 0;
    }

    hbm = CreateCompatibleBitmap(hdc,20,2);
    ReleaseDC(NULL, hdc);

    if (hbm == NULL) {
	return 0;
    }


    hdc = CreateCompatibleDC(NULL);
    if (hdc == NULL) {
	DeleteObject(hbm);
	return 0;
    }

    hbmT = SelectObject(hdc, hbm);

    if (GetObject(hbm, sizeof(bm), &bm) == 0) {
	SelectObject(hdc, hbmT);
	DeleteObject(hbm);
	DeleteDC(hdc);
	return 0;
    }

    PatBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, BLACKNESS);

    TestSurfaceType(hdc, 0, 0);
    GetBitmapBits(hbm, sizeof(bits), bits);
    u = GetSurfaceType(bits);

    if (u == 0) {
        u = GetSurfaceType(bits + bm.bmWidthBytes);

        if (u)
            u |= BM_BOTTOMTOTOP;
    }

#ifndef _WIN32
    if (u) {
        BYTE _huge *pb;
        UINT dy,w;

        //
        // see if bitmap(s) are huge format
        //
        dy = (UINT)(0x10000l/bm.bmWidthBytes) + 1;
        hbm = CreateCompatibleBitmap(hdc,bm.bmWidth,dy);
        DeleteObject(SelectObject(hdc, hbm));
        PatBlt(hdc, 0, 0, bm.bmWidth, dy, BLACKNESS);

        pb = (BYTE _huge *)LockBitmap(hbm);

        if (pb == NULL || OFFSETOF(pb) != 0)
            ; // cant lock bitmaps
        else {
            u |= BM_CANLOCK;

            w = (dy-1) * bm.bmWidthBytes;

            pb[64l*1024] = 0;
            pb[w] = 0;

            if (u & BM_BOTTOMTOTOP)
                SetPixel(hdc, 0, 0, RGB(255,255,255));
            else
                SetPixel(hdc, 0, dy-1, RGB(255,255,255));

            if (pb[64l*1024] != 0 && pb[w] == 0)
                u |= BM_HUGE;
            else if (pb[64l*1024] == 0 && pb[w] != 0)
                ;
            else
                u = 0;
        }
    }
#endif

    SelectObject(hdc, hbmT);
    DeleteObject(hbm);
    DeleteDC(hdc);

    wBitmapType = u;
    return u;
}

//////////////////////////////////////////////////////////////////////////////
//
//  returns the PDevice of the given physical or memory DC
//
//  return the bitmap type that the display driver uses
//
///////////////////////////////////////////////////////////////////////////////

LPVOID FAR GetPDevice(HDC hdc)
{
    HANDLE h;
    HBITMAP hbm;
    HBITMAP hbmT;
    HDC hdcT=NULL;
    IBITMAP FAR *pbm;
    LPVOID lpPDevice = NULL;

    // GDI.403
    static HANDLE (FAR PASCAL *GdiGetBitmapHandle)(HBITMAP hbm, HANDLE h);

    if (GdiGetBitmapHandle == NULL)
        (FARPROC)GdiGetBitmapHandle = GetProcAddress(GetModuleHandle(TEXT("GDI")),(LPCSTR)MAKEINTATOM(403));

    if (GdiGetBitmapHandle == NULL)
        return NULL;

    hbm = CreateBitmap(1,1,1,1,NULL);

    //
    //  first try the passed DC if it is a bitmap/DC
    //
    hbmT = SelectBitmap(hdc, hbm);

    if (hbmT != NULL)
    {
        //
        // it is a memory DC.
        //
        h = GdiGetBitmapHandle(hbmT, 0);
    }
    else
    {
        //
        // it is a physical DC.
        //

        hdcT = CreateCompatibleDC(hdc);
        hbmT = SelectBitmap(hdcT, hbm);

        h = GdiGetBitmapHandle(hbm, 0);
    }

    if (h == NULL)
        goto exit;

    pbm = (IBITMAP FAR *)GlobalLock(h);

    if (IsBadReadPtr(pbm, sizeof(IBITMAP)))
        goto exit;

    if (pbm)
        pbm = (IBITMAP FAR *)pbm->bmlpPDevice;
    else
        pbm = NULL;

    if (IsBadReadPtr(pbm, 2))
        goto exit;

    lpPDevice = (LPVOID)pbm;

exit:
    if (hdcT)
    {
        SelectObject(hdcT, hbmT);
        DeleteObject(hdcT);
    }
    else
    {
        SelectObject(hdc, hbmT);
    }

    DeleteObject(hbm);

    return lpPDevice;
}