/* * 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; }