/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * [Ported back from libfadden]
 *
 * Implementation of DIBitmap class.  This began life as a simple DIBSection
 * wrapper, and has gradually evolved into a bitmap class that creates
 * DIBSections and DDBs when necessary.
 *
 * BMP file format is:
 *	file header (BITMAPFILEHEADER)
 *	information header (BITMAPINFO; size determined by header.vfOffBits)
 *		information (BITMAPINFOHEADER)
 *		color table (RGBQUAD[])
 *	pixel bits
 *
 * The DIBSECTION struct includes BITMAP and BITMAPINFOHEADER.
 *
 * The DIB we create doesn't really have a color table.  The color table is
 * set when we convert to a DDB or write the bitmap to a file.
 */
#include "stdafx.h"
#include "MyDIBitmap.h"
#include "Util.h"

//#include "libfadden.h"
//using namespace libfadden;


/*
 * Destroy allocated memory and delete the DIB object.
 */
MyDIBitmap::~MyDIBitmap(void)
{
	if (mhBitmap != nil)
		::DeleteObject(mhBitmap);
	delete[] mpFileBuffer;
	delete[] mpColorTable;

	/* fpPixels point to system-allocated memory inside fhBitmap */
}

/*
 * Create a blank DIB with the requested dimensions.
 *
 * We probably don't need to allocate DIB storage here.  We can do most
 * operations ourselves, in local memory, and only convert when needed.
 *
 * Returns a pointer to the pixel storage on success, or nil on failure.
 */
void*
MyDIBitmap::Create(int width, int height, int bitsPerPixel, int colorsUsed,
	bool dibSection /*=false*/)
{
	if (mhBitmap != nil || mpPixels != nil || mpFileBuffer != nil) {
		WMSG0(" DIB GLITCH: already created\n");
		assert(false);
		return nil;
	}

	assert(width > 0 && height > 0);
	assert(bitsPerPixel == 1 ||
		   bitsPerPixel == 4 ||
		   bitsPerPixel == 8 ||
		   bitsPerPixel == 16 ||
		   bitsPerPixel == 24 ||
		   bitsPerPixel == 32);
	assert(bitsPerPixel == 24 || bitsPerPixel == 32 || colorsUsed > 0);

	// should include a warning if line stride is not a multiple of 4 bytes

	mBitmapInfoHdr.biSize = sizeof(mBitmapInfoHdr);	// BITMAPINFOHEADER
	mBitmapInfoHdr.biWidth = width;
	mBitmapInfoHdr.biHeight = height;
	mBitmapInfoHdr.biPlanes = 1;
	mBitmapInfoHdr.biBitCount = bitsPerPixel;
	mBitmapInfoHdr.biCompression = BI_RGB;		// has implications for 16-bit
	mBitmapInfoHdr.biSizeImage = 0;
	mBitmapInfoHdr.biXPelsPerMeter = 0;
	mBitmapInfoHdr.biYPelsPerMeter = 0;
	mBitmapInfoHdr.biClrUsed = colorsUsed;
	mBitmapInfoHdr.biClrImportant = 0;

	mNumColorsUsed = colorsUsed;
	if (colorsUsed) {
		mpColorTable = new RGBQUAD[colorsUsed];
		if (mpColorTable == nil)
			return nil;
	}

	if (dibSection) {
		/*
		 * Create an actual blank DIB section.
		 */
		mhBitmap = ::CreateDIBSection(nil, (BITMAPINFO*) &mBitmapInfoHdr,
						DIB_RGB_COLORS, &mpPixels, nil, 0);
		if (mhBitmap == nil) {
			DWORD err = ::GetLastError();
			//CString msg;
			//GetWin32ErrorString(err, &msg);
			//WMSG2(" DIB CreateDIBSection failed (err=%d msg='%s')\n",
			//	err, msg);
			WMSG1(" DIB CreateDIBSection failed (err=%d)\n", err);
			LogHexDump(&mBitmapInfoHdr, sizeof(BITMAPINFO));
			WMSG1("&mpPixels = 0x%08lx\n", &mpPixels);
			DebugBreak();
			return nil;
		}

		/*
		* Save some bitmap statistics.
		*/
		BITMAP info;
		int gotten;
		/* or GetObject(hBitmap, sizeof(DIBSECTION), &dibsection) */
		gotten = ::GetObject(mhBitmap, sizeof(info), &info);
		if (gotten != sizeof(info))
			return nil;
		mPitchBytes = info.bmWidthBytes;
	} else {
		/*
		 * Create a buffer in memory.
		 */
		mPitchBytes = ((width * mBitmapInfoHdr.biBitCount) +7) / 8;
		mPitchBytes = (mPitchBytes + 3) & ~(0x03);		// 32-bit bounds

		/* we're not allocating full file buffer; should be okay */
		mpFileBuffer = new char[mPitchBytes * mBitmapInfoHdr.biHeight];
		mpPixels = mpFileBuffer;
	}

	mWidth = width;
	mHeight = height;
	mBpp = bitsPerPixel;

	/* clear the bitmap, possibly not needed for DIB section */
	assert(mpPixels != nil);
	memset(mpPixels, 0, mPitchBytes * mBitmapInfoHdr.biHeight);

	//WMSG2("+++  allocated %d bytes for bitmap pixels (mPitchBytes=%d)\n",
	//	mPitchBytes * mBitmapInfoHdr.biHeight, mPitchBytes);

	return mpPixels;
}

/*
 * Open the file and call the FILE* version.
 */
int
MyDIBitmap::CreateFromFile(const char* fileName)
{
	FILE* fp = nil;
	int err;

	fp = fopen(fileName, "rb");
	if (fp == nil) {
		err = errno ? errno : -1;
		WMSG2("Unable to read bitmap from file '%s' (err=%d)\n", fileName, err);
		return err;
	}

	long fileLen;
	fseek(fp, 0, SEEK_END);
	fileLen = ftell(fp);
	rewind(fp);

	err = CreateFromFile(fp, fileLen);
	fclose(fp);
	return err;
}

/*
 * Create a DIB by reading a BMP or TGA file into memory.
 */
int
MyDIBitmap::CreateFromFile(FILE* fp, long len)
{
	void* buf = nil;
	int err = -1;

	buf = new unsigned char[len];
	if (buf == nil)
		return err;

	if (fread(buf, len, 1, fp) != 1) {
		err = errno ? errno : -1;
		WMSG2(" DIB failed reading %ld bytes (err=%d)\n", len, err);
		goto bail;
	}

	err = CreateFromNewBuffer(buf, len);

bail:
	return err;
}

/*
 * Create object from a copy of the file in memory.
 *
 * We want to hang on to the data buffer, but if we don't own it then we
 * have to make a copy.
 */
int
MyDIBitmap::CreateFromBuffer(void* buf, long len, bool doDelete)
{
	assert(len > 0);

	if (doDelete) {
		return CreateFromNewBuffer(buf, len);
	} else {
		void* newBuf = new unsigned char[len];
		if (newBuf == nil)
			return -1;
		memcpy(newBuf, buf, len);

		return CreateFromNewBuffer(newBuf, len);
	}
}

/*
 * Create object from a buffer of new[]-created memory that we own.
 *
 * The memory will be discarded if this function fails.
 *
 * We don't want to create a DIB section if the eventual user of this data
 * doesn't need it (e.g. it's just getting converted into a 3D texture
 * without using any GDI calls), so we just leave the pixels in the file
 * buffer for now.
 */
int
MyDIBitmap::CreateFromNewBuffer(void* vbuf, long len)
{
	BITMAPFILEHEADER* pHeader = (BITMAPFILEHEADER*) vbuf;
	unsigned char* buf = (unsigned char*) vbuf;

	assert(pHeader != nil);
	assert(len > 0);

	if (len > 16 && pHeader->bfType == kBMPMagic && (long) pHeader->bfSize == len)
	{
		return ImportBMP(vbuf, len);
	} else if (len > 16 && buf[0x01] == 0 &&
		buf[0x02] == 2 && buf[0x05] == 0 && buf[0x06] == 0 &&
		(buf[0x10] == 16 || buf[0x10] == 24 || buf[0x10] == 32))
	{
		return ImportTGA(vbuf, len);
	} else {
		WMSG2(" DIB invalid bitmap file (type=0x%04x size=%ld)\n",
			pHeader->bfType, pHeader->bfSize);
		delete[] vbuf;
		return -1;
	}
}

/*
 * Set up internal structures for the BMP file.
 *
 * On error, "vbuf" is discarded.
 */
int
MyDIBitmap::ImportBMP(void* vbuf, long len)
{
	BITMAPFILEHEADER* pHeader = (BITMAPFILEHEADER*) vbuf;
	BITMAPINFO* pInfo;
	unsigned char* pBits;
	int err = -1;

	pInfo = (BITMAPINFO*) (pHeader+1);
	pBits = (unsigned char*) pHeader + pHeader->bfOffBits;

	// no need to memset mBitmapInfoHdr; that's done during object construction

	if (pInfo->bmiHeader.biSize == sizeof(BITMAPCOREHEADER)) {
		/* deal with older bitmaps */
		BITMAPCOREHEADER* pCore = (BITMAPCOREHEADER*) pInfo;
		assert(mBitmapInfoHdr.biSize == 0);		// we memset in constructor
		mBitmapInfoHdr.biSize = sizeof(mBitmapInfoHdr);	// BITMAPINFOHEADER
		mBitmapInfoHdr.biWidth = pCore->bcWidth;
		mBitmapInfoHdr.biHeight = pCore->bcSize;
		mBitmapInfoHdr.biPlanes = pCore->bcPlanes;
		mBitmapInfoHdr.biBitCount = pCore->bcBitCount;
		mBitmapInfoHdr.biCompression = BI_RGB;
	} else {
		/* has at least a BITMAPINFOHEADER in it, use existing fields */
		assert(mBitmapInfoHdr.biSize == 0);		// we memset in constructor
		assert(pInfo->bmiHeader.biSize >= sizeof(BITMAPINFOHEADER));
		mBitmapInfoHdr.biSize = sizeof(mBitmapInfoHdr);	// BITMAPINFOHEADER
		mBitmapInfoHdr.biWidth = pInfo->bmiHeader.biWidth;
		mBitmapInfoHdr.biHeight = pInfo->bmiHeader.biHeight;
		mBitmapInfoHdr.biPlanes = pInfo->bmiHeader.biPlanes;
		mBitmapInfoHdr.biBitCount = pInfo->bmiHeader.biBitCount;
		mBitmapInfoHdr.biCompression = pInfo->bmiHeader.biCompression;
	}

	mWidth = mBitmapInfoHdr.biWidth;
	mHeight = mBitmapInfoHdr.biHeight;
	mBpp = mBitmapInfoHdr.biBitCount;
	mNumColorsUsed = mBitmapInfoHdr.biClrUsed;
	mPitchBytes = ((mWidth * mBitmapInfoHdr.biBitCount) +7) / 8;
	mPitchBytes = (mPitchBytes + 3) & ~(0x03);		// round up to mult of 4
	//WMSG3(" DIB +++ width=%d bits=%d pitch=%d\n", mWidth,
	//	mBitmapInfoHdr.biBitCount, mPitchBytes);

	/* prepare the color table, if any */
	if (mBpp <= 8) {
		if (mNumColorsUsed == 0) {
			mNumColorsUsed = 1 << mBpp;
			mBitmapInfoHdr.biClrUsed = mNumColorsUsed;
		}
		mpColorTable = new RGBQUAD[mNumColorsUsed];
		if (mpColorTable == nil)
			goto bail;
		SetColorTable(pInfo->bmiColors);
	}

	/* use the buffered bits */
	mpPixels = pBits;
	mpFileBuffer = vbuf;
	err = 0;

bail:
	if (err != 0)
		delete[] vbuf;
	return err;
}

/*
 * Set up internal structures for the TGA file.
 *
 * We handle 16-, 24-, and 32-bit .TGA files only.  They happen to use the
 * same byte layout as BMP files, so we do very little work here.  If we
 * tried to write the raw data to a BMP file we could end up in trouble,
 * because we don't force the "pitch must be multiple of 4 bytes" rule.
 *
 * On error, "vbuf" is discarded.
 */
int
MyDIBitmap::ImportTGA(void* vbuf, long len)
{
	TargaHeader targaHdr;
	unsigned char* hdr = (unsigned char*) vbuf;
	unsigned char* pBits;
	int err = -1;

	/* pull the header out of the file */
	targaHdr.idLength = hdr[0x00];
	targaHdr.colorMapType = hdr[0x01];
	targaHdr.imageType = hdr[0x02];
	targaHdr.colorMapOrigin = hdr[0x03] | hdr[0x04] << 8;
	targaHdr.colorMapLen = hdr[0x05] | hdr[0x06] << 8;
	targaHdr.colorMapEntryLen = hdr[0x07];
	targaHdr.xOffset = hdr[0x08] | hdr[0x09] << 8;
	targaHdr.yOffset = hdr[0x0a] | hdr[0x0b] << 8;
	targaHdr.width = hdr[0x0c] | hdr[0x0d] << 8;
	targaHdr.height = hdr[0x0e] | hdr[0x0f] << 8;
	targaHdr.bitsPerPixel = hdr[0x10];
	targaHdr.imageDescriptor = hdr[0x11];

	pBits = hdr + kTargaHeaderLen + targaHdr.idLength;

	// no need to memset mBitmapInfoHdr; that's done during object construction

	assert(mBitmapInfoHdr.biSize == 0);		// we memset in constructor
	mBitmapInfoHdr.biSize = sizeof(mBitmapInfoHdr);	// BITMAPINFOHEADER
	mBitmapInfoHdr.biWidth = targaHdr.width;
	mBitmapInfoHdr.biHeight = targaHdr.height;
	mBitmapInfoHdr.biPlanes = 1;
	mBitmapInfoHdr.biBitCount = targaHdr.bitsPerPixel;
	mBitmapInfoHdr.biCompression = BI_RGB;

	mWidth = mBitmapInfoHdr.biWidth;
	mHeight = mBitmapInfoHdr.biHeight;
	mBpp = mBitmapInfoHdr.biBitCount;
	mNumColorsUsed = mBitmapInfoHdr.biClrUsed;
	mPitchBytes = ((mWidth * mBitmapInfoHdr.biBitCount) +7) / 8;
	if ((mPitchBytes & 0x03) != 0) {
		/* should only be a problem if we try to save to BMP or conv to DIB */
		WMSG1(" DIB WARNING: pitchBytes=%d in TGA may not work\n",
			mPitchBytes);
	}
//	mPitchBytes = (mPitchBytes + 3) & ~(0x03);		// round up to power of 2

/*
|        |        |  This field specifies (width) x (height) pixels.  Each     |
|        |        |  pixel specifies an RGB color value, which is stored as    |
|        |        |  an integral number of bytes.                              |
|        |        |                                                            |
|        |        |  The 2 byte entry is broken down as follows:               |
|        |        |  ARRRRRGG GGGBBBBB, where each letter represents a bit.    |
|        |        |  But, because of the lo-hi storage order, the first byte   |
|        |        |  coming from the file will actually be GGGBBBBB, and the   |
|        |        |  second will be ARRRRRGG. "A" represents an attribute bit. |
|        |        |                                                            |
|        |        |  The 3 byte entry contains 1 byte each of blue, green,     |
|        |        |  and red.                                                  |
|        |        |                                                            |
|        |        |  The 4 byte entry contains 1 byte each of blue, green,     |
|        |        |  red, and attribute.  For faster speed (because of the     |
|        |        |  hardware of the Targa board itself), Targa 24 images are  |
|        |        |  sometimes stored as Targa 32 images.                      |
|        |        |                                                            |
*/

	/* use the buffered bits */
	mpPixels = pBits;
	mpFileBuffer = vbuf;
	err = 0;
	//WMSG1("+++ successfully imported %d-bit TGA\n", mBpp);

	if (mBpp == 32) {
		/* 32-bit TGA is a full-alpha format */
		mAlphaType = kAlphaFull;
	}

//bail:
	if (err != 0)
		delete[] vbuf;
	return err;
}


/*
 * If the bitmap wasn't initially created as a DIB section, transform it now
 * so the application can use it in GDI calls.
 *
 * Returns 0 on success, -1 on error.
 */
int
MyDIBitmap::ConvertBufToDIBSection(void)
{
	void* oldPixels = mpPixels;

	assert(mhBitmap == nil);
	assert(mpFileBuffer != nil);

	WMSG0(" DIB converting buf to DIB Section\n");

	/* alloc storage */
	mpPixels = nil;
	mhBitmap = ::CreateDIBSection(nil, (BITMAPINFO*) &mBitmapInfoHdr,
					DIB_RGB_COLORS, &mpPixels, nil, 0);
	if (mhBitmap == nil) {
		DWORD err = ::GetLastError();
		WMSG1(" DIB CreateDIBSection failed (err=%d)\n", err);
		LogHexDump(&mBitmapInfoHdr, sizeof(BITMAPINFO));
		WMSG1("&mpPixels = 0x%08lx\n", &mpPixels);
		DebugBreak();
		mpPixels = oldPixels;
		return -1;
	}
	assert(mpPixels != nil);

	/*
	 * This shouldn't be necessary; I don't think a DIB section uses a
	 * different bmWidthBytes than a file on disk.  If it does, we'll have
	 * to scan-convert it.
	 */
	BITMAP info;
	int gotten;
	// or GetObject(hBitmap, sizeof(DIBSECTION), &dibsection)
	gotten = ::GetObject(mhBitmap, sizeof(info), &info);
	if (gotten != sizeof(info))
		return nil;
	assert(mPitchBytes == info.bmWidthBytes);
	//mPitchBytes = info.bmWidthBytes;

	/* copy the bits in */
	memcpy(mpPixels, oldPixels, mPitchBytes * mHeight);

	/* throw out the old storage */
	delete[] mpFileBuffer;
	mpFileBuffer = nil;

	return 0;
}

/*
 * Create the object from a resource embedded in the application.
 *
 * Use MAKEINTRESOURCE to load a resource by ordinal.
 */
void*
MyDIBitmap::CreateFromResource(HINSTANCE hInstance, const char* rsrc)
{
	mhBitmap = (HBITMAP) ::LoadImage(hInstance, rsrc, IMAGE_BITMAP, 0, 0,
					LR_DEFAULTCOLOR | LR_CREATEDIBSECTION);
	if (mhBitmap == nil) {
		DWORD err = ::GetLastError();
		//CString msg;
		//GetWin32ErrorString(err, &msg);
		//WMSG2(" DIB CreateDIBSection failed (err=%d msg='%s')\n",
		//	err, msg);
		WMSG1(" DIB LoadImage failed (err=%d)\n", err);
		return nil;
	}

	/*
	 * Pull out bitmap details.
	 */
	DIBSECTION info;
	int gotten;
	gotten = ::GetObject(mhBitmap, sizeof(info), &info);
	if (gotten != sizeof(info))
		return nil;
	mPitchBytes = info.dsBm.bmWidthBytes;
	mWidth = info.dsBm.bmWidth;
	mHeight = info.dsBm.bmHeight;
	mBpp = info.dsBm.bmBitsPixel;
	mpPixels = info.dsBm.bmBits;
	mNumColorsUsed = info.dsBmih.biClrUsed;

	if (mBpp <= 8) {
		if (mNumColorsUsed == 0)
			mNumColorsUsed = 1 << mBpp;
		mpColorTable = new RGBQUAD[mNumColorsUsed];
		if (mpColorTable == nil)
			goto bail;		// should reset mpPixels?

		/*
		 * Extracting the color table from a DIB is annoying.  I don't
		 * entirely understand the need for all these HDC gymnastics, but
		 * it doesn't work without both handles.
		 */
		HDC tmpDC;
		HDC memDC;
		HGDIOBJ oldBits;
		int count;

		tmpDC = GetDC(nil);
		assert(tmpDC != nil);
		memDC = CreateCompatibleDC(tmpDC);
		oldBits = SelectObject(memDC, mhBitmap);
		count = GetDIBColorTable(memDC, 0, mNumColorsUsed, mpColorTable);
		if (count == 0) {
			DWORD err = ::GetLastError();
			CString buf;
			GetWin32ErrorString(err, &buf);
			WMSG2(" DIB GetDIBColorTable failed (err=0x%x '%s')\n", err, buf);
		}
		SelectObject(memDC, oldBits);
		DeleteDC(memDC);
		ReleaseDC(nil, tmpDC);
	}

	/*
	 * Unfortunately it appears that LoadImage sets up the mPitchBytes
	 * improperly for a DIB.  I believe DIBs need 4-byte-aligned widths
	 * while compatible bitmaps only need 2-byte-aligned.  We need to
	 * tweak mPitchBytes or we'll get garbage.
	 */
	if (mPitchBytes & 0x03) {
		WMSG1(" DIB altering LoadImage pitchBytes (currently %d)\n", mPitchBytes);
		mPitchBytes = (mPitchBytes + 3) & ~(0x03);
	}

bail:
	assert(mpPixels != nil);
	return mpPixels;
}
#if 0	// this might be a better way??
            HRSRC   hResInfo;
            HGLOBAL hResData;
            DWORD   dwSize;
            VOID*   pvRes;
           // Loading it as a file failed, so try it as a resource
            if( NULL == ( hResInfo = FindResource( NULL, strFileName, TEXT("WAVE") ) ) )
            {
                if( NULL == ( hResInfo = FindResource( NULL, strFileName, TEXT("WAV") ) ) )
                    return DXTRACE_ERR( TEXT("FindResource"), E_FAIL );
            }

            if( NULL == ( hResData = LoadResource( NULL, hResInfo ) ) )
                return DXTRACE_ERR( TEXT("LoadResource"), E_FAIL );

            if( 0 == ( dwSize = SizeofResource( NULL, hResInfo ) ) ) 
                return DXTRACE_ERR( TEXT("SizeofResource"), E_FAIL );

            if( NULL == ( pvRes = LockResource( hResData ) ) )
                return DXTRACE_ERR( TEXT("LockResource"), E_FAIL );

            m_pResourceBuffer = new CHAR[ dwSize ];
            memcpy( m_pResourceBuffer, pvRes, dwSize );
#endif


/*
 * Zero out a bitmap's pixels.  Does not touch the color table.
 */
void
MyDIBitmap::ClearPixels(void)
{
	assert(mpPixels != nil);

	//WMSG1(" DIB clearing entire bitmap (%d bytes)\n", mPitchBytes * mHeight);
	memset(mpPixels, 0, mPitchBytes * mHeight);
}


/*
 * Set the values in the color table.
 */
void
MyDIBitmap::SetColorTable(const RGBQUAD* pColorTable)
{
	assert(pColorTable != nil);

	/* scan for junk */
	for (int i = 0; i < mNumColorsUsed; i++) {
		if (pColorTable[i].rgbReserved != 0) {
			/*
			 * PhotoShop v5.x sets rgbReserved to 1 on every 8th color table
			 * entry on 8-bit images.  No idea why.
			 */
			//WMSG2(" DIB warning: bogus color entry %d (res=%d)\n", i,
			//	pColorTable[i].rgbReserved);
			//DebugBreak();
		}
	}

	/* structs are the same, so just copy it over */
	memcpy(mpColorTable, pColorTable, mNumColorsUsed * sizeof(RGBQUAD));
	mColorTableInitialized = true;
}


/*
 * Retrieve the transparency color key, if any.
 *
 * Returns "false" if no color key has been set.
 */
bool
MyDIBitmap::GetTransparentColor(RGBQUAD* pColor) const
{
	if (mAlphaType != kAlphaTransparency)
		return false;
	*(DWORD*)pColor = mTransparentColor;
	return true;
}

/*
 * Set the transparent color.  Changes the alpha mode to kAlphaTransparency.
 */
void
MyDIBitmap::SetTransparentColor(const RGBQUAD* pColor)
{
	if (mAlphaType == kAlphaFull) {
		WMSG0(" NOTE: switching from full alpha to transparent-color alpha\n");
	}
	mTransparentColor = *(const DWORD*)pColor;
	mTransparentColor &= ~kAlphaMask;	// strip alpha off, want color only
	mAlphaType = kAlphaTransparency;
}


/*
 * Look up an RGB color in an indexed color table.
 *
 * Returns the index of the color, or -1 if not found (-2 on error, e.g. this
 * isn't an indexed-color bitmap or the color table hasn't been created).
 */
int
MyDIBitmap::LookupColor(const RGBQUAD* pRgbQuad)
{
	if (mBpp > 8) {
		WMSG1(" DIB LookupColor on %d-bit image\n", mBpp);
		return -2;
	}
	if (!mColorTableInitialized) {
		WMSG0(" DIB can't LookupColor, color table not initialized\n");
		return -2;
	}

	/* set the rgbReserved field to zero */
	unsigned long color = *(unsigned long*) pRgbQuad;
	color &= ~(kAlphaMask);

	int idx;
	for (idx = 0; idx < mNumColorsUsed; idx++) {
		if (color == *(unsigned long*)&mpColorTable[idx])
			return idx;
	}

	return -1;
}


/*
 * Return the RGB value of a single pixel in a bitmap.
 *
 * "rgbReserved" is always set to zero.
 */
void
MyDIBitmap::GetPixelRGB(int x, int y, RGBQUAD* pRgbQuad) const
{
	GetPixelRGBA(x, y, pRgbQuad);
	pRgbQuad->rgbReserved = 0;
}

/*
 * Return the RGBA value of a single pixel in a bitmap.
 *
 * This sets rgbReserved appropriately for the current alpha mode.
 */
void
MyDIBitmap::GetPixelRGBA(int x, int y, RGBQUAD* pRgbQuad) const
{
	assert(x >= 0 && x < mWidth && y >= 0 && y < mHeight);
	y = mHeight - y -1;	// upside-down

	if (mBpp == 32) {
		assert((mPitchBytes % 4) == 0);
		assert(sizeof(RGBQUAD) == 4);

		RGBQUAD* lptr = (RGBQUAD*) mpPixels;
		lptr += y * (mPitchBytes >> 2) + x;
		*pRgbQuad = *lptr;
	} else if (mBpp == 24) {
		unsigned char* ptr = (unsigned char*) mpPixels;

		ptr += y * mPitchBytes + (x << 1) + x;
		pRgbQuad->rgbBlue = *ptr++;
		pRgbQuad->rgbGreen = *ptr++;
		pRgbQuad->rgbRed = *ptr++;
		//pRgbQuad->rgbReserved = 0;
	} else if (mBpp == 16) {
		/* format is XRRRRRGGGGGBBBBB; must convert 0-31 to 0-255 */
		static const unsigned int conv[32] = {
			0,   8,  16,  25,  33,  41,  49,  58, 
			66,  74,  82,  90,  99, 107, 115, 123, 
			132, 140, 148, 156, 165, 173, 181, 189, 
			197, 206, 214, 222, 230, 239, 247, 255
		};
		unsigned short* ptr = (unsigned short*) mpPixels;
		unsigned short val;

		ptr += y * (mPitchBytes >> 1) + x;
		val = *ptr;
		pRgbQuad->rgbBlue = conv[val & 0x1f];
		pRgbQuad->rgbGreen = conv[(val >> 5) & 0x1f];
		pRgbQuad->rgbRed = conv[(val >> 10) & 0x1f];
		//pRgbQuad->rgbReserved = 0;
	} else if (mBpp == 8) {
		unsigned char* ptr = (unsigned char*) mpPixels;
		int idx;

		ptr += y * mPitchBytes + x;
		idx = *ptr;
		*pRgbQuad = mpColorTable[idx];
	} else if (mBpp == 4) {
		assert(mpColorTable != nil);
		unsigned char* ptr = (unsigned char*) mpPixels;
		int idx;

		ptr += y * mPitchBytes + (x >> 1);
		if (x & 0x01)
			idx = (*ptr & 0x0f);
		else
			idx = (*ptr & 0xf0) >> 4;
		*pRgbQuad = mpColorTable[idx];
	} else if (mBpp == 1) {
		assert(sizeof(RGBQUAD) == sizeof(DWORD));

		unsigned char* ptr = (unsigned char*) mpPixels;
		ptr += y * mPitchBytes + (x >> 3);
		if (*ptr & (0x80 >> (x & 0x07)))
			*(DWORD*)pRgbQuad = 0xffffff00;
		else
			*(DWORD*)pRgbQuad = 0x00000000;
	} else {
		assert(false);	// bit depth not implemented
	}

	/*
	 * Fix up the "rgbReserved" field.  Windows says it must always be zero,
	 * so unless the application changes the alpha type, we leave it that way.
	 */
	if (mAlphaType == kAlphaOpaque) {
		pRgbQuad->rgbReserved = 255;	// always force to 255, even on 32bpp
	} else if (mAlphaType == kAlphaTransparency) {
		/* test for the transparent color, ignoring alpha */
		if (((*(DWORD*)pRgbQuad) & ~kAlphaMask) == mTransparentColor)
			pRgbQuad->rgbReserved = 0;				// fully transparent
		else
			pRgbQuad->rgbReserved = 255;			// fully opaque
	} else {
		assert(mAlphaType == kAlphaFull);
		assert(mBpp == 32);
		/* full alpha in 32-bit data, leave it be */
	}
}

/*
 * Set the RGB value of a single pixel in a bitmap.
 *
 * The "rgbReserved" channel is forced to zero.
 */
void
MyDIBitmap::SetPixelRGB(int x, int y, const RGBQUAD* pRgbQuad)
{
	if (pRgbQuad->rgbReserved == 0) {
		SetPixelRGBA(x, y, pRgbQuad);
	} else {
		RGBQUAD tmp = *pRgbQuad;
		tmp.rgbReserved = 0;
		SetPixelRGBA(x, y, &tmp);
	}
}

/*
 * Set the RGBA value of a single pixel in a bitmap.
 *
 * For index-color bitmaps, this requires a (slow) table lookup.
 */
void
MyDIBitmap::SetPixelRGBA(int x, int y, const RGBQUAD* pRgbQuad)
{
	assert(x >= 0 && x < mWidth && y >= 0 && y < mHeight);
	y = mHeight - y -1;	// upside-down

	if (mBpp == 32) {
		assert((mPitchBytes % 4) == 0);
		assert(sizeof(RGBQUAD) == 4);

		RGBQUAD* lptr = (RGBQUAD*) mpPixels;
		lptr += y * (mPitchBytes >> 2) + x;
		*lptr = *pRgbQuad;
	} else if (mBpp == 24) {
		unsigned char* ptr = (unsigned char*) mpPixels;

		ptr += y * mPitchBytes + (x << 1) + x;
		*ptr++ = pRgbQuad->rgbBlue;
		*ptr++ = pRgbQuad->rgbGreen;
		*ptr++ = pRgbQuad->rgbRed;
	} else if (mBpp == 8 || mBpp == 4) {
		int idx = LookupColor(pRgbQuad);
		if (idx < 0) {
			WMSG3(" DIB WARNING: unable to set pixel to (%d,%d,%d)\n",
				pRgbQuad->rgbRed, pRgbQuad->rgbGreen, pRgbQuad->rgbBlue);
		} else {
			SetPixelIndex(x, (mHeight - y -1), idx);
		}
	} else {
		assert(false);	// not implemented
	}
}

/*
 * Get the color table index of the specified pixel.
 *
 * Only works on indexed-color formats (8bpp or less).
 */
void
MyDIBitmap::GetPixelIndex(int x, int y, int* pIdx) const
{
	assert(x >= 0 && x < mWidth && y >= 0 && y < mHeight);
	y = mHeight - y -1;	// upside-down

	if (mBpp == 8) {
		unsigned char* ptr = (unsigned char*) mpPixels;

		ptr += y * mPitchBytes + x;
		*pIdx = *ptr;
	} else if (mBpp == 4) {
		unsigned char* ptr = (unsigned char*) mpPixels;

		ptr += y * mPitchBytes + (x >> 1);
		if (x & 0x01)
			*pIdx = (*ptr & 0x0f);
		else
			*pIdx = (*ptr & 0xf0) >> 4;
	} else {
		assert(false);		// not implemented
	}
}

/*
 * Set the index value of a pixel in an indexed-color bitmap (8bpp or less).
 */
void
MyDIBitmap::SetPixelIndex(int x, int y, int idx)
{
	if (x < 0 || x >= mWidth || y < 0 || y >= mHeight) {
		WMSG3("BAD x=%d y=%d idx=%d\n", x, y, idx);
		WMSG2("   width=%d height=%d\n", mWidth, mHeight);
	}
	assert(x >= 0 && x < mWidth && y >= 0 && y < mHeight);
	y = mHeight - y -1;	// upside-down

	if (mBpp == 8) {
		assert(idx >= 0 && idx < 256);
		unsigned char* ptr = (unsigned char*) mpPixels;

		ptr += y * mPitchBytes + x;
		*ptr = idx;
	} else if (mBpp == 4) {
		assert(idx >= 0 && idx < 16);
		unsigned char* ptr = (unsigned char*) mpPixels;

		ptr += y * mPitchBytes + (x >> 1);
		if (x & 0x01)
			*ptr = (*ptr & 0xf0) | idx;
		else
			*ptr = (*ptr & 0x0f) | idx << 4;
	} else {
		assert(false);	// not implemented
	}
}


/*
 * Blit a block of pixels from one bitmap to another.
 *
 * The bitmaps must share a common format, and the rectangles must be the
 * same size.  We could implement color conversion and resizing here, but
 * for now let's not.
 */
/*static*/ bool
MyDIBitmap::Blit(MyDIBitmap* pDstBits, const RECT* pDstRect,
	const MyDIBitmap* pSrcBits, const RECT* pSrcRect)
{
	if (pDstRect->right - pDstRect->left !=
		pSrcRect->right - pSrcRect->left)
	{
		WMSG0("DIB blit: widths differ\n");
		return false;
	}
	if (pDstRect->bottom - pDstRect->top !=
		pSrcRect->bottom - pSrcRect->top)
	{
		WMSG0("DIB blit: heights differ\n");
		return false;
	}
	if (pSrcBits->mBpp != pDstBits->mBpp) {
		WMSG0("DIB blit: different formats\n");
		return false;
	}
	if (pDstRect->right <= pDstRect->left ||
		pDstRect->bottom <= pDstRect->top)
	{
		WMSG0("DIB blit: poorly formed rect\n");
		return false;
	}

	int srcX, srcY, dstX, dstY;
	srcY = pSrcRect->top;
	dstY = pDstRect->top;

	/*
	 * A decidedly non-optimized blit function.
	 *
	 * Copy by index when appropriate.
	 */
	if (pDstBits->mBpp <= 8) {
		int idx;
		while (srcY < pSrcRect->bottom) {
			srcX = pSrcRect->left;
			dstX = pDstRect->left;
			while (srcX < pSrcRect->right) {
				pSrcBits->GetPixelIndex(srcX, srcY, &idx);
				pDstBits->SetPixelIndex(dstX, dstY, idx);
				srcX++;
				dstX++;
			}
			srcY++;
			dstY++;
		}
	} else {
		RGBQUAD color;
		while (srcY < pSrcRect->bottom) {
			srcX = pSrcRect->left;
			dstX = pDstRect->left;
			while (srcX < pSrcRect->right) {
				pSrcBits->GetPixelRGBA(srcX, srcY, &color);
				pDstBits->SetPixelRGBA(dstX, dstY, &color);
				srcX++;
				dstX++;
			}
			srcY++;
			dstY++;
		}
	}

	return true;
}


/*
 * Create a DDB from the current bitmap in the specified DC, and return its
 * handle.  The returned handle must eventually be disposed with DeleteObject.
 *
 * Since we're just supplying pointers to various pieces of data, there's no
 * need for us to have a DIB section.
 *
 * Returns nil on failure.
 */
HBITMAP
MyDIBitmap::ConvertToDDB(HDC dc) const
{
	HBITMAP hBitmap = nil;

	if (mNumColorsUsed != 0 && !mColorTableInitialized) {
		WMSG0(" DIB color table not initialized!\n");
		return nil;
	}

	/*
	 * Create a BITMAPINFO structure with the BITMAPINFOHEADER from the
	 * DIB and a copy of the color table (if any).
	 *
	 * (We slightly over-allocate here, because the size of the BITMAPINFO
	 * struct actually includes the first color entry.)
	 */
	BITMAPINFO* pNewInfo = nil;
	int colorTableSize = sizeof(RGBQUAD) * mNumColorsUsed;
	pNewInfo = (BITMAPINFO*)
					new unsigned char[sizeof(BITMAPINFO) + colorTableSize];
	if (pNewInfo == nil)
		return nil;

	pNewInfo->bmiHeader = mBitmapInfoHdr;
	if (colorTableSize != 0)
		memcpy(&pNewInfo->bmiColors, mpColorTable, colorTableSize);

#if 0		// this fails under Win98SE, works under Win2K
	/*
	 * Create storage.
	 */
	hBitmap = ::CreateDIBitmap(dc, &mBitmapInfoHdr, 0, nil, nil, 0);
	if (hBitmap == nil) {
		WMSG0(" DIB CreateDIBBitmap failed!\n");
		return nil;
	}

	WMSG4(" PARM hbit=0x%08lx hgt=%d fpPixels=0x%08lx pNewInfo=0x%08lx\n",
		hBitmap, mBitmapInfoHdr.biHeight, fpPixels, pNewInfo);
	LogHexDump(&mBitmapInfoHdr, sizeof(mBitmapInfoHdr));
	WMSG("  pNewInfo (sz=%d colorTableSize=%d):\n", sizeof(BITMAPINFO),
		colorTableSize);
	LogHexDump(pNewInfo, sizeof(BITMAPINFO) + colorTableSize);

	/*
	 * Transfer the bits.
	 */
	int count = ::SetDIBits(nil, hBitmap, 0, mBitmapInfoHdr.biHeight,
					mpPixels, pNewInfo, DIB_RGB_COLORS);

	if (count != mBitmapInfoHdr.biHeight) {
		DWORD err = ::GetLastError();

		WMSG1(" DIB SetDIBits failed, count was %d\n", count);
		::DeleteObject(hBitmap);
		hBitmap = nil;

		CString msg;
		GetWin32ErrorString(err, &msg);
		WMSG2(" DIB CreateDIBSection failed (err=%d msg='%s')\n",
			err, msg);
		//ASSERT(false);		// stop & examine this
		return nil;
	}
#else
	/*
	 * Create storage.
	 */
	hBitmap = ::CreateDIBitmap(dc, &mBitmapInfoHdr, CBM_INIT, mpPixels,
					pNewInfo, DIB_RGB_COLORS);
	if (hBitmap == nil) {
		WMSG0(" DIB CreateDIBBitmap failed!\n");
		return nil;
	}
#endif

	delete[] pNewInfo;
	return hBitmap;
}


/*
 * Write the bitmap to the named file.  Opens the file and calls the FILE*
 * function.
 */
int
MyDIBitmap::WriteToFile(const char* fileName) const
{
	FILE* fp = nil;
	int err;

	assert(fileName != nil);

	fp = fopen(fileName, "wb");
	if (fp == nil) {
		err = errno ? errno : -1;
		WMSG2("Unable to open bitmap file '%s' (err=%d)\n", fileName, err);
		return err;
	}

	err = WriteToFile(fp);
	fclose(fp);
	return err;
}

/*
 * Write the bitmap to a file.
 *
 * Pass in an open, seeked file pointer (make sure to use "wb" mode).
 *
 * Returns 0 on success, or nonzero (errno) on failure.
 */
int
MyDIBitmap::WriteToFile(FILE* fp) const
{
	BITMAPFILEHEADER fileHeader;
	long pixelBufSize;
	long startOffset;
	int result = -1;

	assert(fp != nil);

	startOffset = ftell(fp);

	/* make sure all GDI operations on this bitmap have completed */
	GdiFlush();

	pixelBufSize = mPitchBytes * mBitmapInfoHdr.biHeight;

	fileHeader.bfType = kBMPMagic;
	fileHeader.bfReserved1 = 0;
	fileHeader.bfReserved2 = 0;
	fileHeader.bfOffBits = sizeof(fileHeader) + sizeof(mBitmapInfoHdr) +
		sizeof(RGBQUAD) * mNumColorsUsed;
	fileHeader.bfSize = fileHeader.bfOffBits + pixelBufSize;

	WMSG3(" DIB writing bfOffBits=%d, bfSize=%d, pixelBufSize=%d\n",
		fileHeader.bfOffBits, fileHeader.bfSize, pixelBufSize);

	if (fwrite(&fileHeader, sizeof(fileHeader), 1, fp) != 1) {
		result = errno ? errno : -1;
		goto bail;
	}
	if (fwrite(&mBitmapInfoHdr, sizeof(mBitmapInfoHdr), 1, fp) != 1) {
		result = errno ? errno : -1;
		goto bail;
	}
	if (mNumColorsUsed != 0) {
		assert(mpColorTable != nil);
		if (fwrite(mpColorTable, sizeof(RGBQUAD) * mNumColorsUsed, 1, fp) != 1)
		{
			result = errno ? errno : -1;
			goto bail;
		}
	}
	if (fwrite(mpPixels, pixelBufSize, 1, fp) != 1) {
		result = errno ? errno : -1;
		goto bail;
	}

	/* push it out to disk */
	fflush(fp);

	/* verify the length; useful for detecting "w" vs "wb" */
	if (ftell(fp) - startOffset != (long) fileHeader.bfSize) {
		WMSG2("DIB tried to write %ld, wrote %ld (check for \"wb\")\n",
			fileHeader.bfSize, ftell(fp) - startOffset);
		assert(false);
	}

	result = 0;

bail:
	return result;
}
