ciderpress/util/MyDIBitmap.cpp
Andy McFadden f6647b9978 Convert WSMG to LOG
Mostly a bulk conversion of debug messages, primarily with sed:

 sed -e 's/\(WMSG[0-9]\)\(.*\)\(\\n"\)/LOGI\2"/'

This removes the '\n' from the end of the log messages, and sets
them all to "info" severity.

We want to prefix each line with file/line and/or a timestamp,
so it doesn't make sense to have a partial line, and there's no
value in embedding the '\n' in every string.
2014-11-18 14:16:35 -08:00

1177 lines
36 KiB
C++

/*
* 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 != NULL)
::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 NULL on failure.
*/
void*
MyDIBitmap::Create(int width, int height, int bitsPerPixel, int colorsUsed,
bool dibSection /*=false*/)
{
if (mhBitmap != NULL || mpPixels != NULL || mpFileBuffer != NULL) {
LOGI(" DIB GLITCH: already created");
assert(false);
return NULL;
}
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 == NULL)
return NULL;
}
if (dibSection) {
/*
* Create an actual blank DIB section.
*/
mhBitmap = ::CreateDIBSection(NULL, (BITMAPINFO*) &mBitmapInfoHdr,
DIB_RGB_COLORS, &mpPixels, NULL, 0);
if (mhBitmap == NULL) {
DWORD err = ::GetLastError();
//CString msg;
//GetWin32ErrorString(err, &msg);
//LOGI(" DIB CreateDIBSection failed (err=%d msg='%ls')",
// err, (LPCWSTR) msg);
LOGI(" DIB CreateDIBSection failed (err=%d)", err);
LogHexDump(&mBitmapInfoHdr, sizeof(BITMAPINFO));
LOGI("&mpPixels = 0x%08lx", &mpPixels);
DebugBreak();
return NULL;
}
/*
* 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 NULL;
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 != NULL);
memset(mpPixels, 0, mPitchBytes * mBitmapInfoHdr.biHeight);
//LOGI("+++ allocated %d bytes for bitmap pixels (mPitchBytes=%d)",
// mPitchBytes * mBitmapInfoHdr.biHeight, mPitchBytes);
return mpPixels;
}
/*
* Open the file and call the FILE* version.
*/
int
MyDIBitmap::CreateFromFile(const WCHAR* fileName)
{
FILE* fp = NULL;
int err;
fp = _wfopen(fileName, L"rb");
if (fp == NULL) {
err = errno ? errno : -1;
LOGI("Unable to read bitmap from file '%ls' (err=%d)",
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 = NULL;
int err = -1;
buf = new unsigned char[len];
if (buf == NULL)
return err;
if (fread(buf, len, 1, fp) != 1) {
err = errno ? errno : -1;
LOGI(" DIB failed reading %ld bytes (err=%d)", 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 == NULL)
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 != NULL);
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 {
LOGI(" DIB invalid bitmap file (type=0x%04x size=%ld)",
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
//LOGI(" DIB +++ width=%d bits=%d pitch=%d", 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 == NULL)
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 */
LOGI(" DIB WARNING: pitchBytes=%d in TGA may not work",
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;
//LOGI("+++ successfully imported %d-bit TGA", 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 == NULL);
assert(mpFileBuffer != NULL);
LOGI(" DIB converting buf to DIB Section");
/* alloc storage */
mpPixels = NULL;
mhBitmap = ::CreateDIBSection(NULL, (BITMAPINFO*) &mBitmapInfoHdr,
DIB_RGB_COLORS, &mpPixels, NULL, 0);
if (mhBitmap == NULL) {
DWORD err = ::GetLastError();
LOGI(" DIB CreateDIBSection failed (err=%d)", err);
LogHexDump(&mBitmapInfoHdr, sizeof(BITMAPINFO));
LOGI("&mpPixels = 0x%08lx", &mpPixels);
DebugBreak();
mpPixels = oldPixels;
return -1;
}
assert(mpPixels != NULL);
/*
* 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 NULL;
assert(mPitchBytes == info.bmWidthBytes);
//mPitchBytes = info.bmWidthBytes;
/* copy the bits in */
memcpy(mpPixels, oldPixels, mPitchBytes * mHeight);
/* throw out the old storage */
delete[] mpFileBuffer;
mpFileBuffer = NULL;
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 WCHAR* rsrc)
{
mhBitmap = (HBITMAP) ::LoadImage(hInstance, rsrc, IMAGE_BITMAP, 0, 0,
LR_DEFAULTCOLOR | LR_CREATEDIBSECTION);
if (mhBitmap == NULL) {
DWORD err = ::GetLastError();
//CString msg;
//GetWin32ErrorString(err, &msg);
//LOGI(" DIB CreateDIBSection failed (err=%d msg='%ls')",
// err, (LPCWSTR) msg);
LOGI(" DIB LoadImage failed (err=%d)", err);
return NULL;
}
/*
* Pull out bitmap details.
*/
DIBSECTION info;
int gotten;
gotten = ::GetObject(mhBitmap, sizeof(info), &info);
if (gotten != sizeof(info))
return NULL;
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 == NULL)
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(NULL);
assert(tmpDC != NULL);
memDC = CreateCompatibleDC(tmpDC);
oldBits = SelectObject(memDC, mhBitmap);
count = GetDIBColorTable(memDC, 0, mNumColorsUsed, mpColorTable);
if (count == 0) {
DWORD err = ::GetLastError();
CString buf;
GetWin32ErrorString(err, &buf);
LOGI(" DIB GetDIBColorTable failed (err=0x%x '%ls')",
err, (LPCWSTR) buf);
}
SelectObject(memDC, oldBits);
DeleteDC(memDC);
ReleaseDC(NULL, 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) {
LOGI(" DIB altering LoadImage pitchBytes (currently %d)", mPitchBytes);
mPitchBytes = (mPitchBytes + 3) & ~(0x03);
}
bail:
assert(mpPixels != NULL);
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 != NULL);
//LOGI(" DIB clearing entire bitmap (%d bytes)", mPitchBytes * mHeight);
memset(mpPixels, 0, mPitchBytes * mHeight);
}
/*
* Set the values in the color table.
*/
void
MyDIBitmap::SetColorTable(const RGBQUAD* pColorTable)
{
assert(pColorTable != NULL);
/* 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.
*/
//LOGI(" DIB warning: bogus color entry %d (res=%d)", 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) {
LOGI(" NOTE: switching from full alpha to transparent-color alpha");
}
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) {
LOGI(" DIB LookupColor on %d-bit image", mBpp);
return -2;
}
if (!mColorTableInitialized) {
LOGI(" DIB can't LookupColor, color table not initialized");
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 != NULL);
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) {
LOGI(" DIB WARNING: unable to set pixel to (%d,%d,%d)",
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) {
LOGI("BAD x=%d y=%d idx=%d", x, y, idx);
LOGI(" width=%d height=%d", 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)
{
LOGI("DIB blit: widths differ");
return false;
}
if (pDstRect->bottom - pDstRect->top !=
pSrcRect->bottom - pSrcRect->top)
{
LOGI("DIB blit: heights differ");
return false;
}
if (pSrcBits->mBpp != pDstBits->mBpp) {
LOGI("DIB blit: different formats");
return false;
}
if (pDstRect->right <= pDstRect->left ||
pDstRect->bottom <= pDstRect->top)
{
LOGI("DIB blit: poorly formed rect");
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 NULL on failure.
*/
HBITMAP
MyDIBitmap::ConvertToDDB(HDC dc) const
{
HBITMAP hBitmap = NULL;
if (mNumColorsUsed != 0 && !mColorTableInitialized) {
LOGI(" DIB color table not initialized!");
return NULL;
}
/*
* 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 = NULL;
int colorTableSize = sizeof(RGBQUAD) * mNumColorsUsed;
pNewInfo = (BITMAPINFO*)
new unsigned char[sizeof(BITMAPINFO) + colorTableSize];
if (pNewInfo == NULL)
return NULL;
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, NULL, NULL, 0);
if (hBitmap == NULL) {
LOGI(" DIB CreateDIBBitmap failed!");
return NULL;
}
LOGI(" PARM hbit=0x%08lx hgt=%d fpPixels=0x%08lx pNewInfo=0x%08lx",
hBitmap, mBitmapInfoHdr.biHeight, fpPixels, pNewInfo);
LogHexDump(&mBitmapInfoHdr, sizeof(mBitmapInfoHdr));
LOGI(" pNewInfo (sz=%d colorTableSize=%d):\n", sizeof(BITMAPINFO),
colorTableSize);
LogHexDump(pNewInfo, sizeof(BITMAPINFO) + colorTableSize);
/*
* Transfer the bits.
*/
int count = ::SetDIBits(NULL, hBitmap, 0, mBitmapInfoHdr.biHeight,
mpPixels, pNewInfo, DIB_RGB_COLORS);
if (count != mBitmapInfoHdr.biHeight) {
DWORD err = ::GetLastError();
LOGI(" DIB SetDIBits failed, count was %d", count);
::DeleteObject(hBitmap);
hBitmap = NULL;
CString msg;
GetWin32ErrorString(err, &msg);
LOGI(" DIB CreateDIBSection failed (err=%d msg='%ls')",
err, (LPCWSTR) msg);
//ASSERT(false); // stop & examine this
return NULL;
}
#else
/*
* Create storage.
*/
hBitmap = ::CreateDIBitmap(dc, &mBitmapInfoHdr, CBM_INIT, mpPixels,
pNewInfo, DIB_RGB_COLORS);
if (hBitmap == NULL) {
LOGI(" DIB CreateDIBBitmap failed!");
return NULL;
}
#endif
delete[] pNewInfo;
return hBitmap;
}
/*
* Write the bitmap to the named file. Opens the file and calls the FILE*
* function.
*/
int
MyDIBitmap::WriteToFile(const WCHAR* fileName) const
{
FILE* fp = NULL;
int err;
assert(fileName != NULL);
fp = _wfopen(fileName, L"wb");
if (fp == NULL) {
err = errno ? errno : -1;
LOGI("Unable to open bitmap file '%ls' (err=%d)", 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 != NULL);
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;
LOGI(" DIB writing bfOffBits=%d, bfSize=%d, pixelBufSize=%d",
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 != NULL);
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) {
LOGI("DIB tried to write %ld, wrote %ld (check for \"wb\")",
fileHeader.bfSize, ftell(fp) - startOffset);
assert(false);
}
result = 0;
bail:
return result;
}