mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-29 20:49:27 +00:00
d35a9670b5
We weren't handling oddly sized images well. This was mostly a problem for Apple Preferred Format, PNT/$0002.
1044 lines
33 KiB
C++
1044 lines
33 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;
|
|
|
|
|
|
MyDIBitmap::~MyDIBitmap(void)
|
|
{
|
|
if (mhBitmap != NULL)
|
|
::DeleteObject(mhBitmap);
|
|
delete[] mpFileBuffer;
|
|
delete[] mpColorTable;
|
|
|
|
/* fpPixels point to system-allocated memory inside fhBitmap */
|
|
}
|
|
|
|
void* MyDIBitmap::Create(int width, int height, int bitsPerPixel, int colorsUsed,
|
|
bool dibSection /*=false*/)
|
|
{
|
|
// We probably don't need to allocate DIB storage here. We can do most
|
|
// operations ourselves, in local memory, and only convert when needed.
|
|
|
|
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
|
|
if ((width & 0x03) != 0) {
|
|
LOGW(" DIB stride must be multiple of 4 bytes (got %d)", width);
|
|
}
|
|
|
|
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);
|
|
LOGE(" DIB CreateDIBSection failed (err=%d)", err);
|
|
LogHexDump(&mBitmapInfoHdr, sizeof(BITMAPINFO));
|
|
LOGI(" &mpPixels = 0x%p", &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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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%p", &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;
|
|
}
|
|
|
|
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);
|
|
LOGW(" 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
|
|
|
|
void MyDIBitmap::ClearPixels(void)
|
|
{
|
|
assert(mpPixels != NULL);
|
|
|
|
//LOGI(" DIB clearing entire bitmap (%d bytes)", mPitchBytes * mHeight);
|
|
memset(mpPixels, 0, mPitchBytes * mHeight);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool MyDIBitmap::GetTransparentColor(RGBQUAD* pColor) const
|
|
{
|
|
if (mAlphaType != kAlphaTransparency)
|
|
return false;
|
|
*(DWORD*)pColor = mTransparentColor;
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
/*static*/ bool MyDIBitmap::Blit(MyDIBitmap* pDstBits, const RECT* pDstRect,
|
|
const MyDIBitmap* pSrcBits, const RECT* pSrcRect)
|
|
{
|
|
if (pDstRect->right - pDstRect->left !=
|
|
pSrcRect->right - pSrcRect->left)
|
|
{
|
|
LOGW("DIB blit: widths differ");
|
|
return false;
|
|
}
|
|
if (pDstRect->bottom - pDstRect->top !=
|
|
pSrcRect->bottom - pSrcRect->top)
|
|
{
|
|
LOGW("DIB blit: heights differ");
|
|
return false;
|
|
}
|
|
if (pSrcBits->mBpp != pDstBits->mBpp) {
|
|
LOGW("DIB blit: different formats");
|
|
return false;
|
|
}
|
|
if (pDstRect->right <= pDstRect->left ||
|
|
pDstRect->bottom <= pDstRect->top)
|
|
{
|
|
LOGW("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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|