Click here to Skip to main content
15,881,204 members
Articles / Multimedia / OpenGL

Generating Outlines in OpenGL

Rate me:
Please Sign up or sign in to vote.
4.78/5 (12 votes)
7 Oct 20045 min read 198.7K   5K   52  
Using multi-pass techniques to generate outlines in OpenGL.
// CRGBSurface.cpp - a wrapper to DIB section GDI object
//
// (c) W.Weyna 'Voytec'


#include "stdafx.h"
#include "OGLT.h"
#include "CRGBSurface.h"

#define ENABLE_JPEG	

// JPEG compressor/decompressor
#ifdef ENABLE_JPEG
#include "jpeglib.h"
#include <setjmp.h>
#endif

#define DIB_MAGIC	0x4d42

#define SIZEOF_BITMAPFILEHEADER_PACKED  (   \
    sizeof(WORD) +      /* bfType      */   \
    sizeof(DWORD) +     /* bfSize      */   \
    sizeof(WORD) +      /* bfReserved1 */   \
    sizeof(WORD) +      /* bfReserved2 */   \
    sizeof(DWORD))      /* bfOffBits   */

//******************************************************
//
// CRGBSurface()
//
//******************************************************

CRGBSurface::CRGBSurface()
{
	m_hDIBSection = NULL;
	m_pDIBits = NULL;
	m_hDIBSectionOld = NULL;
	m_pDC = NULL;

	m_bSaveFile_FileExists = false;
}


//******************************************************
//
// ~CRGBSurface()
//
//******************************************************


CRGBSurface::~CRGBSurface()
{
	DeleteDC();
	Delete();
}


//******************************************************
//
// Delete()
//
//******************************************************

void CRGBSurface::Delete()
{
   if(m_hDIBSectionOld && m_pDC)
   {
		::SelectObject(m_pDC->m_hDC, m_hDIBSectionOld);
		m_hDIBSectionOld = NULL;
   }

	if(m_hDIBSection)
		::DeleteObject(m_hDIBSection);

	m_hDIBSection = NULL;
	
	m_pDIBits = NULL;
}


//******************************************************
//
// DeleteDC()
//
//******************************************************

void CRGBSurface::DeleteDC()
{
	if(m_pDC)
	{
	   if(m_hDIBSectionOld)
	   {
		   	::SelectObject(m_pDC->m_hDC, m_hDIBSectionOld);
			m_hDIBSectionOld = NULL;
	   }
		delete m_pDC; 
	}
	m_pDC = NULL;
}


//******************************************************
//
// RecreateDC()
//
//******************************************************

bool CRGBSurface::RecreateDC()
{
	if(m_pDC != NULL)
		return true;

	if(!m_hDIBSection)
	{
		ASSERT(0);
		return false;
	}
	
	// If programme asserts here it means that
	// you called DeleteDC() in your code and
	// you probably shouldn't have done it, because
	// we need a DC now. Remove a DeleteDC() from your code
	// or remove an ASSERT(0) here if you want a DC to 
	// be recreated.
	
	ASSERT(0);

	// Recreate DC.
	m_pDC = new CDC;
	if(!m_pDC->CreateCompatibleDC(NULL))
	{
		ASSERT(0);
		return false;
	}
	
	m_hDIBSectionOld = (HBITMAP)::SelectObject(m_pDC->m_hDC, m_hDIBSection);

	return true;
}


//******************************************************
//
// Create()
//
//******************************************************


bool CRGBSurface::Create(int cx, int cy, int nBitsPerPixel /* = 24 */) 
{
	ASSERT(nBitsPerPixel == 24 || nBitsPerPixel == 32);
	ASSERT(cx > 0);
	ASSERT(cy > 0);

	// If same sizes as previous surface,
	// don't create a new surface,
	// only make sure that all members are valid,
	// and that DIB section is selected into DC.
	if(m_pDC != NULL && m_bih.biWidth == cx && m_bih.biHeight == cy &&
	    m_bih.biBitCount == nBitsPerPixel && m_hDIBSection && m_pDIBits)
	{
 		SelectObject(m_pDC->m_hDC, m_hDIBSection);
		return true;
	}

	// Destroy previous surface if we are recreating.
	Delete();
	
	// Create surface DC if not created yet.
	if(m_pDC == NULL)
	{
		m_pDC = new CDC;
		if(!m_pDC->CreateCompatibleDC(NULL))
		{
			ASSERT(0);
			return false;
		}
	}

	// Fill BITMAPINFOHEADER.
	int nSize = sizeof(BITMAPINFOHEADER);
	ZeroMemory(&m_bih, nSize);

	m_bih.biSize = nSize;
	m_bih.biWidth = cx;
	m_bih.biHeight = cy;
	m_bih.biPlanes = 1;
	m_bih.biBitCount = nBitsPerPixel;
	m_bih.biCompression = BI_RGB;

	// Create the DIB section.
	m_hDIBSection = CreateDIBSection(m_pDC->m_hDC,
							(BITMAPINFO*)&m_bih,
							DIB_RGB_COLORS,
							(void**)&m_pDIBits,
							NULL,
							0);

	ASSERT(m_hDIBSection);
	ASSERT(m_pDIBits);

	if(!m_hDIBSection || !m_pDIBits)
		return false;

    // Select the new DIB section into the surface DC
    if(m_hDIBSection)
		m_hDIBSectionOld = (HBITMAP)::SelectObject(m_pDC->m_hDC, m_hDIBSection);

	return true;
}


//*******************************************************
//
// Create()
//
//*******************************************************

bool CRGBSurface::Create(HBITMAP hBitmap, int nBitsPerPixel /* = 24 */, CDC* pBitmapDC /* = NULL */)
{
	ASSERT(nBitsPerPixel == 24 || nBitsPerPixel == 32);

	// Get bitmap sizes
	BITMAP bm;
	if(!GetObject(hBitmap, sizeof(BITMAP), &bm))
	{
		ASSERT(0);
		return false;
	}

	// Create new DIB section
	Create(bm.bmWidth, bm.bmHeight, nBitsPerPixel); 

	// Create a DC for pBitmap DDB if pBitmapDC not supplied
	bool bWeCreatedDC = false;
	if(pBitmapDC == NULL)
	{
		pBitmapDC = new CDC;
		VERIFY(pBitmapDC->CreateCompatibleDC(NULL));
		bWeCreatedDC = true;
	}

	HGDIOBJ hOld = ::SelectObject(pBitmapDC->m_hDC, hBitmap);

	// Convert DDB to surface DIB section
	m_pDC->BitBlt(0, 0, bm.bmWidth, bm.bmHeight, pBitmapDC, 
		0, 0, SRCCOPY);

	::SelectObject(pBitmapDC->m_hDC, hOld);

	if(bWeCreatedDC)
		delete pBitmapDC;
	
	return true;
}


//*******************************************************
//
// Create()
//
//*******************************************************

bool CRGBSurface::Create(const char* szPath, int nBitsPerPixel /* = 24 */)
{
	ASSERT(nBitsPerPixel == 24 || nBitsPerPixel == 32);
	
	CString strPath = szPath;
	CString strExt = strPath.Right(3);

#ifdef ENABLE_JPEG	
	// JPEG
	if(!strExt.CompareNoCase(_T("jpg")))
		return CreateFromJPEGFile(strPath, nBitsPerPixel);
	
	// Stereoscopic JPEG
	if(!strExt.CompareNoCase(_T("jps")))
		return CreateFromJPEGFile(strPath, nBitsPerPixel);
#endif
	
	// DIB
	if(!strExt.CompareNoCase(_T("bmp")))
		return CreateFromDIBFile(strPath, nBitsPerPixel);

	if(!strExt.CompareNoCase(_T("dib")))
		return CreateFromDIBFile(strPath, nBitsPerPixel);

	// Unsupported file type
	ASSERT(0);
	return false;
}


bool CRGBSurface::CreateFromDIBFile(const CString& strPath, int nBitsPerPixel /* = 24 */)
{
	ASSERT(nBitsPerPixel == 24 || nBitsPerPixel == 32);

	// Load DIB from file to memory.
	BYTE* pDIB = LoadDIB(strPath);
	if(pDIB == NULL)
		return false; 

	// Convert from memory DIB to a pure RGB DIB section;
	bool bRetVal = Create(pDIB, nBitsPerPixel);

	delete pDIB;

	return bRetVal;
}


//*******************************************************
//
// Create()
//
//*******************************************************


bool CRGBSurface::Create(HANDLE hFile, int nBitsPerPixel /* = 24 */)
{
	ASSERT(nBitsPerPixel == 24 || nBitsPerPixel == 32);

	// Load DIB from file to memory.
	BYTE* pDIB = LoadDIBFromOpenedFile(hFile);
	if(pDIB == NULL)
		return false; 

	// Convert from memory DIB to a pure RGB DIB section;
	bool bRetVal = Create(pDIB, nBitsPerPixel);

	delete pDIB;

	return bRetVal;
}


//*******************************************************
//
// Create()
//
//*******************************************************

bool CRGBSurface::Create(CRGBSurface* const pSourceSurface)
{
	if(!Create(pSourceSurface->GetWidth(), 
		   pSourceSurface->GetHeight(),
		   pSourceSurface->m_bih.biBitCount))
	{
		ASSERT(0);
		return false;
	}
	return pSourceSurface->CopyTo(this);
}


//*******************************************************
//
// Create()
//
//*******************************************************

bool CRGBSurface::Create(BYTE* pDIB, int nBitsPerPixel /* = 24 */)
{
	ASSERT(nBitsPerPixel == 24 || nBitsPerPixel == 32);

	if(pDIB == NULL)
	{
		ASSERT(0);
		return false; 
	}

	// Create new DIB section
	Create(((BITMAPINFOHEADER*)pDIB)->biWidth,
	       ((BITMAPINFOHEADER*)pDIB)->biHeight, nBitsPerPixel);

	// Initialize created DIB section using memory DIB.
	// A convertion from a memory DIB with a color table or
	// different number of bits per pixel
	// to a 24 bit or 32 bit RGB DIB section 
	// is done here by SetDIBits().
	if(!SetDIBits(m_pDC->m_hDC,
		          m_hDIBSection,
			      0,
			      m_bih.biHeight,
				  GetPointerToDIBits(pDIB),
			      (BITMAPINFO*)pDIB,
			      DIB_RGB_COLORS))
	{
		ASSERT(0);
		return false;
	}

	return true;
}


//******************************************************
//
// Draw()
//
//******************************************************


void CRGBSurface::Draw(CDC* pDestDC, int x, int y, int destWidth /* = -1 */, int destHeight /* = -1 */)  const
{
	GdiFlush();

	if(destWidth < 0)
		destWidth = m_bih.biWidth;
	if(destHeight < 0)
		destHeight = m_bih.biHeight;

	CSize bitmapDestSize(destWidth, destHeight);
	CPoint ptDestPosition(x, y);

	if(pDestDC->GetMapMode() != MM_TEXT)
	{
		// Convert DIB sizes to currently used logical units,
		pDestDC->DPtoLP(&bitmapDestSize);
		pDestDC->DPtoLP(&ptDestPosition);
	}

	pDestDC->SetStretchBltMode(COLORONCOLOR); 

	if(!StretchDIBits(pDestDC->m_hDC, 
		ptDestPosition.x, ptDestPosition.y,
		bitmapDestSize.cx, bitmapDestSize.cy,
		0, 0,
		m_bih.biWidth, m_bih.biHeight,
		m_pDIBits,
		(BITMAPINFO*)&m_bih,
		DIB_RGB_COLORS,
		SRCCOPY))
	{
		ASSERT(0);
	}
	
	GdiFlush();
}


void CRGBSurface::Draw(CDC* pDestDC, int srcx, int srcy, int srcWidth, int srcHeight,
		int destx, int desty, int destWidth /* =-1 */, int destHeight /* =-1 */)  const
{
	GdiFlush();

	ASSERT(srcx+srcWidth<=m_bih.biWidth);
	ASSERT(srcy+srcHeight<=m_bih.biHeight);
		
	if(destWidth < 0)
		destWidth = m_bih.biWidth;
	if(destHeight < 0)
		destHeight = m_bih.biHeight;

	CSize bitmapDestSize(destWidth, destHeight);
	CSize bitmapSrcSize(srcWidth, srcHeight);
	CPoint ptDestPosition(destx, desty);
	CPoint ptSrcPosition(srcx, srcy);

	if(pDestDC->GetMapMode() != MM_TEXT)
	{
		// Convert DIB sizes to currently used logical units,
		pDestDC->DPtoLP(&bitmapDestSize);
		pDestDC->DPtoLP(&ptDestPosition);
	}

	pDestDC->SetStretchBltMode(COLORONCOLOR); 

	if(!StretchDIBits(pDestDC->m_hDC, 
		ptDestPosition.x, ptDestPosition.y,
		bitmapDestSize.cx, bitmapDestSize.cy,
		ptSrcPosition.x, ptSrcPosition.y,
		bitmapSrcSize.cx, bitmapSrcSize.cy,
		m_pDIBits,
		(BITMAPINFO*)&m_bih,
		DIB_RGB_COLORS,
		SRCCOPY))
	{
		ASSERT(0);
	}
	
	GdiFlush();
}

//******************************************************
//
// Blt()
//
//******************************************************

void CRGBSurface::Blt(CDC* pDestDC, int destWidth, int destHeight) const
{
	GdiFlush();

	if (destWidth == -1)
		destWidth = m_bih.biWidth;
	if (destHeight == -1)
		destHeight = m_bih.biHeight;

	if(!SetDIBitsToDevice(pDestDC->m_hDC, 
		0, 0,
        destWidth, destHeight,
		0, 0,
		0,
		m_bih.biHeight,
		m_pDIBits,
		(BITMAPINFO*)&m_bih,
		DIB_RGB_COLORS ))
	{
		ASSERT(0);
	}

	GdiFlush();
}

void CRGBSurface::Blt(CDC* pDestDC, int srcX, int srcY, int destX, int destY, int destWidth, int destHeight ) const
{
	GdiFlush();

	if (destWidth == -1)
		destWidth = m_bih.biWidth;
	if (destHeight == -1)
		destHeight = m_bih.biHeight;

	ASSERT(srcX+destWidth <= m_bih.biWidth);
	ASSERT(destX+destWidth <= m_bih.biWidth);
	ASSERT(srcY+destHeight <= m_bih.biHeight);
	ASSERT(destY+destHeight <= m_bih.biHeight);

	if(!SetDIBitsToDevice(pDestDC->m_hDC, 
		destX, destY,
        destWidth, destHeight,
		srcX, srcY,
		0,
		m_bih.biHeight,
		m_pDIBits,
		(BITMAPINFO*)&m_bih,
		DIB_RGB_COLORS ))
	{
		ASSERT(0);
	}

	GdiFlush();
}


bool CRGBSurface::CopyToClipboard(bool bNoCopy /* = false */) 
{
	if(!m_hDIBSection)
		return false;

	if(!::OpenClipboard(NULL))
		return false;
	
	if(!EmptyClipboard())
		return false;

	GdiFlush();

	if(bNoCopy)
	{
		if(!SetClipboardData(CF_BITMAP, m_hDIBSection))
			return false;
	}
	else
	{
		if(!SetClipboardData(CF_DIB, CreateDIB_Win16()))
			return false;
	}

	if(bNoCopy)
		m_hDIBSection = NULL;	// We no longer own this object

	if(!CloseClipboard())
		return false;

	return true;
}


//*******************************************************
//
// CreateDIB_Win16()
//
//*******************************************************


HGLOBAL CRGBSurface::CreateDIB_Win16()
{
	if(!m_hDIBSection || !m_pDIBits)
	{
		// Call Create() first.
		ASSERT(0);
		return NULL;
	}

	RecreateDC();  // check if DC is valid and if not, recreate

	GdiFlush();

	ASSERT(GetSize_Bytes());

	HGLOBAL hDIB = GlobalAlloc(GMEM_MOVEABLE, sizeof(BITMAPINFOHEADER) + GetSize24_Bytes());
	BYTE* pDIB = (BYTE*)GlobalLock(hDIB);
	CopyMemory(pDIB, &m_bih, sizeof(BITMAPINFOHEADER));
	((BITMAPINFOHEADER*)pDIB)->biBitCount = 24;
	BYTE* pDIBits = pDIB + sizeof(BITMAPINFOHEADER);

	if(!GetDIBits(m_pDC->m_hDC, 
				  m_hDIBSection,
				  0,
				  m_bih.biHeight,
				  (LPVOID)pDIBits,
				  (LPBITMAPINFO)pDIB,
				  DIB_RGB_COLORS))
	{
		GlobalFree(hDIB);
		return NULL;
	}

 
	GlobalUnlock(hDIB);
	
	ASSERT(hDIB);

	return hDIB;
}


//*******************************************************
//
// CreateDIB()
//
//*******************************************************

BYTE* CRGBSurface::CreateDIB() 
{
	if(!m_hDIBSection || !m_pDIBits)
	{
		// Call Create() first.
		ASSERT(0);
		return NULL;
	}

	RecreateDC();  // check if DC is valid and if not, recreate	

	GdiFlush();

	ASSERT(GetSize_Bytes());

	BYTE* pDIB = (BYTE*)malloc(sizeof(BITMAPINFOHEADER) + GetSize24_Bytes());
	CopyMemory(pDIB, &m_bih, sizeof(BITMAPINFOHEADER));
	((BITMAPINFOHEADER*)pDIB)->biBitCount = 24;
	BYTE* pDIBits = pDIB + sizeof(BITMAPINFOHEADER);

	if(!GetDIBits(m_pDC->m_hDC, 
				  m_hDIBSection,
				  0,
				  m_bih.biHeight,
				  (LPVOID)pDIBits,
				  (LPBITMAPINFO)pDIB,
				  DIB_RGB_COLORS))
	{
		free(pDIB);
		return NULL;
	}

	return pDIB;
}


//*******************************************************
//
// CopyTo()
//
//*******************************************************

bool CRGBSurface::CopyTo(CRGBSurface* pDestSurface, DWORD dwX /* = 0 */, DWORD dwY /* = 0 */) const
{
	pDestSurface->AssertValid();
	if(*pDestSurface < *this)
	{
		ASSERT(0);
		return false;
	}
	if(dwX >= (DWORD)pDestSurface->GetWidth())
	{
		ASSERT(0);
		return false;
	}
	if(dwY >= (DWORD)pDestSurface->GetHeight())
	{
		ASSERT(0);
		return false;
	}

	if(!pDestSurface->GetDC()->BitBlt(dwX, dwY, GetWidth(), GetHeight(), m_pDC, 0, 0, SRCCOPY))
	{
		ASSERT(0);
		return false;
	}

	return true;
}


////////////////////////////////////////////////////////
// Functions that directly manipulate DIB section bits


void CRGBSurface::Clear(DWORD dwBGRA /* = BGRA(255, 255, 255, 255) */)
{
	GdiFlush();

	switch(m_bih.biBitCount)
	{
	case 32:
		{
		// Fast DWORD copy. 
        for(DWORD* pdwPixel = (DWORD*)m_pDIBits; pdwPixel < (DWORD*)(m_pDIBits + GetSize_Bytes()); pdwPixel++)
			*pdwPixel = dwBGRA;
		}
		break;
	case 24:
		{
		// Check if same value in R, G and B, 
		// so we could use fast FillMemory().
		BYTE bGValue = GetGValue(dwBGRA);
		if(GetRValue(dwBGRA) == bGValue && bGValue ==	GetBValue(dwBGRA))
		{
			FillMemory(m_pDIBits, GetSize_Bytes(), bGValue);
			return;
		}
		// Fill the DIB, take into acount a correct row alignment.
		for(BYTE* pbNextRow = m_pDIBits; 
			pbNextRow < m_pDIBits + GetSize_Bytes(); 
			pbNextRow += GetAlignedRowSize_Bytes())
		{
			for(BYTE* pbPtr = pbNextRow; 
				pbPtr < pbNextRow + GetRowSize_Bytes(); 
				pbPtr += 3)
			{
				*(DWORD*)pbPtr = dwBGRA;
			}
		}		
		break;
		}
	default:
		ASSERT(0);
	}
}


//******************************************************
//
// ClearCR()
//
//******************************************************

void CRGBSurface::ClearCR(DWORD dwRGBA /* = RGBA(255, 255, 255, 255) */)
{
	Clear(COLORREFtoBGRA(dwRGBA));
}


//******************************************************
//
// Brightness()
//
//******************************************************

void CRGBSurface::Brightness(double dBrightness)
{
	DWORD dwByte;
	BYTE* pNextLine = GetPixels();
	for(int y = 0; y < m_bih.biHeight; y++, pNextLine += GetAlignedRowSize_Bytes())
	{
		BYTE* pNextPixel = pNextLine;
		for(int x = 0; x < m_bih.biWidth; x++, pNextPixel += GetPixelSize_Bytes())
		{
			dwByte = DWORD(*pNextPixel * dBrightness);
			if(dwByte > 255)
				dwByte = 255;
			*pNextPixel	= (BYTE)dwByte;

			dwByte = DWORD(*(pNextPixel + 1) * dBrightness);
			if(dwByte > 255)
				dwByte = 255;
			*(pNextPixel + 1) = (BYTE)dwByte;

			dwByte = DWORD(*(pNextPixel + 2) * dBrightness);
			if(dwByte > 255)
				dwByte = 255;
			*(pNextPixel + 2) = (BYTE)dwByte;
		}
	}
}


////////////////////////////////////////////////////////
// IO functions

//*******************************************************
//
// LoadDIB()
//
//*******************************************************

BYTE* CRGBSurface::LoadDIB(const CString& strPath)
{
	HANDLE hFile;
	
	hFile = CreateFile(strPath,
				   GENERIC_READ,
				   FILE_SHARE_READ,
				   NULL,
		           OPEN_EXISTING,
				   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
				   NULL);
	
	if(hFile == INVALID_HANDLE_VALUE)
		return NULL;
	
	BYTE* pDIB = LoadDIBFromOpenedFile(hFile);

	CloseHandle(hFile);

	return pDIB;
}


//*******************************************************
//
// LoadDIBFromOpenedFile()
//
//*******************************************************

BYTE* CRGBSurface::LoadDIBFromOpenedFile(HANDLE hFile)
{
	if(hFile == INVALID_HANDLE_VALUE)
	{
		ASSERT(0);
		return NULL;
	}

	// Get current file pointer.
	int nOrigin = SetFilePointer(hFile, 0, 0, FILE_CURRENT);
	if(nOrigin < 0)
		return NULL;
		
	// Read BITMAPFILEHEADER.
	
	BITMAPFILEHEADER bitmapFileHeader;

	DWORD dwBytesRead;

	if(!ReadFile(hFile, &bitmapFileHeader, sizeof (BITMAPFILEHEADER), &dwBytesRead, NULL))
		return NULL;
	
	// Make sure this is a Microsoft DIB file.

	if((dwBytesRead != sizeof (BITMAPFILEHEADER)) ||
		(bitmapFileHeader.bfType != DIB_MAGIC)) 
	{
		return NULL;
	}

	// Allocate memory for BITMAPINFOHEADER.

	DWORD dwSize = bitmapFileHeader.bfSize - sizeof (BITMAPFILEHEADER);
	BYTE* pDIB = (BYTE*) malloc(sizeof (BITMAPINFOHEADER));
	
	// Read BITMAPINFOHEADER.
	
	if(!ReadFile(hFile, pDIB, sizeof (BITMAPINFOHEADER), &dwBytesRead, NULL))
	{
		free(pDIB);
		return NULL;
	}
	if(dwBytesRead != sizeof (BITMAPINFOHEADER))
	{
		free(pDIB);
		return NULL;
	}

	BITMAPINFOHEADER* pBitmapInfo = (BITMAPINFOHEADER*)pDIB;

	// Fill empty fields.

	// DWORD alligned image size in bytes.
	if(pBitmapInfo->biSizeImage == 0)
	{
        pBitmapInfo->biSizeImage = 
			ALIGN(pBitmapInfo->biWidth * pBitmapInfo->biBitCount / 8, 4) * pBitmapInfo->biHeight;
	}

	// Number of entries in the color table
    if(pBitmapInfo->biClrUsed == 0)
	{
	    switch(pBitmapInfo->biBitCount)
		{
		case 1:	
			pBitmapInfo->biClrUsed = 2;
			break;
		case 4: 
			pBitmapInfo->biClrUsed = 16;
			break;
		case 8: 
			pBitmapInfo->biClrUsed = 256;
		}
	}
	
	DWORD dwColorTableSize = pBitmapInfo->biClrUsed * sizeof(RGBQUAD);
	
	// Change allocated BITMAPINFOHEADER memory block
	// to a memory block for a complete DIB.
	pDIB = (BYTE*)realloc(pDIB, sizeof (BITMAPINFOHEADER) + dwColorTableSize + pBitmapInfo->biSizeImage);
	pBitmapInfo = (BITMAPINFOHEADER*)pDIB;

	// Load the color table.
    if(pBitmapInfo->biClrUsed)
	{
		RGBQUAD* pColorTable = (RGBQUAD*)(pDIB + pBitmapInfo->biSize);
		if(!ReadFile(hFile, pColorTable, dwColorTableSize, &dwBytesRead, NULL))
		{
			free(pDIB);
			return NULL;
		}
		if(dwBytesRead != dwColorTableSize)
		{
			free(pDIB);
			return NULL;
		}
	}

	// Set file pointer where DIB bits are located, and read them.
	if(SetFilePointer(hFile, nOrigin + bitmapFileHeader.bfOffBits, 0, FILE_BEGIN) <0)
	{
		free(pDIB);
		return NULL;
	}

	BYTE* pBitmapBits = pDIB + sizeof(BITMAPINFOHEADER) + dwColorTableSize;
	if(!ReadFile(hFile, pBitmapBits, pBitmapInfo->biSizeImage, &dwBytesRead, NULL))
	{
		free(pDIB);
		return NULL;
	}
	if(dwBytesRead != pBitmapInfo->biSizeImage)
	{
		free(pDIB);
		return NULL;
	}

	return pDIB;
}


//*******************************************************
//
// SaveDIB()
//
//*******************************************************

bool CRGBSurface::SaveDIB(const CString& strPath, bool bAllowOverwrite /* = false */)
{
	HANDLE hFile;
	
	DWORD dwCreationDisposition = CREATE_NEW;
	if(bAllowOverwrite)
		dwCreationDisposition = CREATE_ALWAYS;

	m_bSaveFile_FileExists = false;

	hFile = CreateFile(strPath,
				   GENERIC_WRITE,
				   NULL,
				   NULL,
		           dwCreationDisposition,
				   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
				   NULL);
	
	if(hFile == INVALID_HANDLE_VALUE)
	{
		if(GetLastError() == ERROR_FILE_EXISTS)
			m_bSaveFile_FileExists = true;
		return false;
	}
		
	bool bRet = SaveDIBToOpenedFile(hFile);

	CloseHandle(hFile);

	return bRet;
}


//*******************************************************
//
// SaveDIBToOpenedFile()
//
//*******************************************************

bool CRGBSurface::SaveDIBToOpenedFile(HANDLE hFile)
{
	if(hFile == INVALID_HANDLE_VALUE)
	{
		ASSERT(0);
		return false;
	}

	// Get current file pointer.
	int nOrigin = SetFilePointer(hFile, 0, 0, FILE_CURRENT);
	if(nOrigin < 0)
		return false;
		
	// Construct and write BITMAPFILEHEADER.
	
	BITMAPFILEHEADER bitmapFileHeader;
	
	bitmapFileHeader.bfType = DIB_MAGIC;
    bitmapFileHeader.bfSize = SIZEOF_BITMAPFILEHEADER_PACKED +
		                      sizeof(BITMAPINFOHEADER) + 
						      GetSize_Bytes();
	bitmapFileHeader.bfReserved1 = 0;
    bitmapFileHeader.bfReserved2 = 0;
    bitmapFileHeader.bfOffBits = SIZEOF_BITMAPFILEHEADER_PACKED +
		                         sizeof(BITMAPINFOHEADER); 


	DWORD dwBytesWritten;

	if(!WriteFile(hFile, &bitmapFileHeader, SIZEOF_BITMAPFILEHEADER_PACKED, &dwBytesWritten, NULL))
		return false;
	
	if(dwBytesWritten != SIZEOF_BITMAPFILEHEADER_PACKED)
		return false;

	
	// Construct a 24bit memory DIB from this DIB section,
	// and write it to the file.

	DWORD dwDIBSize = sizeof (BITMAPINFOHEADER) + GetSize24_Bytes();
	BYTE* pDIB = CreateDIB();
	if(pDIB == NULL)
		return false;

	int nResult = WriteFile(hFile, pDIB, dwDIBSize, &dwBytesWritten, NULL);
	
	free(pDIB);

	if(!nResult ||	dwBytesWritten != dwDIBSize)
		return false;
	
	return true;
}


//*******************************************************
//
// GetPointerToDIBits()
//
//*******************************************************

BYTE* CRGBSurface::GetPointerToDIBits(BYTE* pDIB)
{
	BITMAPINFOHEADER* pBitmapInfo = (BITMAPINFOHEADER*)pDIB;
	
	DWORD dwColorTableSize = pBitmapInfo->biClrUsed * sizeof(RGBQUAD);
			
	return pDIB + sizeof(BITMAPINFOHEADER) + dwColorTableSize;
}


//*******************************************************
//
// SaveToFile()
//
//*******************************************************

bool CRGBSurface::SaveToFile(const CString& strPath, bool bAllowOverwrite /* = false */, int nQuality /* = -1 */)
{
	CString strExt = strPath.Right(3);

#ifdef ENABLE_JPEG	
	// JPEG
	if(!strExt.CompareNoCase(_T("jpg")) || !strExt.CompareNoCase(_T("jps")))
		return SaveAsJPEGFile(strPath, nQuality, bAllowOverwrite);
#endif

	if(!strExt.CompareNoCase(_T("bmp")) || !strExt.CompareNoCase(_T("dib")))
		return SaveDIB(strPath, bAllowOverwrite); 

	ASSERT(0);
	return false;
}
	
#ifdef ENABLE_JPEG
#pragma message ("Compiling JPEG routines...")



bool CRGBSurface::CreateFromJPEGFile(const CString& strPath, int nBitsPerPixel /* = 24 */)
{
	// Open stdio file.
	FILE* infile;		
	if((infile = fopen(strPath, "rb"))==NULL)
		return false;

	if(!CreateFromOpenedStdioJPEGFile(infile, nBitsPerPixel))
	{
		fclose(infile);
		return false;
	}

	fclose(infile);
	return true;
}


//*******************************************************
//
// CreateFromOpenedStdioJPEGFile()
//
//*******************************************************

// Needed for JPEG error handling
struct my_error_mgr {
  struct jpeg_error_mgr pub;	
  jmp_buf setjmp_buffer; };
typedef struct my_error_mgr * my_error_ptr;

METHODDEF(void) my_error_exit (j_common_ptr cinfo)
{
  my_error_ptr myerr = (my_error_ptr) cinfo->err;
  longjmp(myerr->setjmp_buffer, 1);  
}

bool CRGBSurface::CreateFromOpenedStdioJPEGFile(FILE* infile, int nBitsPerPixel /* = 24 */)
{
	JSAMPARRAY ppbBuffer;		
	struct my_error_mgr jerr;

	jpeg_decompress_struct cinfo;

	cinfo.err = jpeg_std_error(&jerr.pub);
	jerr.pub.error_exit = my_error_exit;  

	// Set return jump point for longjmp()
	if(setjmp(jerr.setjmp_buffer)) 
	{
		// If we get here, it means that
		// there was an error during decompression.
		jpeg_destroy_decompress(&cinfo);
#ifdef _DEBUG
		char buffer[1024];
		cinfo.err->format_message((jpeg_common_struct*)&cinfo, buffer);
		AfxMessageBox(buffer);
#endif
		return false;
	}

	// Prepeare decompresor object
	jpeg_create_decompress(&cinfo);
	jpeg_stdio_src(&cinfo, infile); 
	jpeg_read_header(&cinfo, TRUE);
	jpeg_start_decompress(&cinfo);

	// Create DIB section
	if(!Create(cinfo.output_width, cinfo.output_height, nBitsPerPixel))
		return false;

	DWORD dwByteWidth = GetRowSize_Bytes();
	DWORD dwAlignedRowSize = GetAlignedRowSize_Bytes();

	BYTE* pbTemp;
	ppbBuffer = &pbTemp;  // pbTemp to avoid alloc_sarray
	*ppbBuffer = m_pDIBits + GetSize_Bytes() - dwAlignedRowSize;	
		
	switch(nBitsPerPixel)
	{
	case 32:
		{
		// Decompresion to 32 bit DIB section.
		while(cinfo.output_scanline < cinfo.output_height)
		{
			jpeg_read_scanlines(&cinfo, ppbBuffer, 1);	
			
			// Swap R and B components and insert A component
			BYTE* pbBuf = *ppbBuffer + cinfo.output_width * 3 - 1;
			for(BYTE* pbBuf32 = *ppbBuffer + dwAlignedRowSize - 1; 
			    pbBuf32 > *ppbBuffer; 
			    pbBuf32 -= 4, pbBuf -= 3)
			{
				*pbBuf32 = 255;
				*(pbBuf32 - 3) = *pbBuf;
				*(pbBuf32 - 2) = *(pbBuf - 1);
				*(pbBuf32 - 1) = *(pbBuf - 2);
			}
			*ppbBuffer -= dwAlignedRowSize;			
		}
		}
		break;
	case 24:
		{
		// Decompresion to 24 bit DIB section
	
		BYTE  bByte;

		while(cinfo.output_scanline < cinfo.output_height)
		{
			jpeg_read_scanlines(&cinfo, ppbBuffer, 1);	

			// swap R and B components
			for(BYTE* pbBuf = *ppbBuffer; pbBuf < *ppbBuffer + dwByteWidth; pbBuf += 3)
			{
				bByte = pbBuf[2];
				pbBuf[2] = *pbBuf;
				*pbBuf = bByte;
			}
			
			*ppbBuffer -= dwAlignedRowSize;			
		}
		}
		break;
	default:
		ASSERT(0);
		return false;
	}

	jpeg_finish_decompress(&cinfo);
	jpeg_destroy_decompress(&cinfo);
	
	return true;
}


//*******************************************************
//
// SaveAsJPEGFile()
//
//*******************************************************

bool CRGBSurface::SaveAsJPEGFile(const CString& strPath, int nQuality /* = -1 */, bool bAllowOverwrite /* = false */)
{
	ASSERT(nQuality >= -1 && nQuality <= 100);

	struct jpeg_compress_struct cinfo;
	struct my_error_mgr jerr;

	FILE* outfile;		
	JSAMPARRAY ppbBuffer;		
	
	cinfo.err = jpeg_std_error(&jerr.pub);
	jerr.pub.error_exit = my_error_exit;  

	// Set return jump point for longjmp()
	if(setjmp(jerr.setjmp_buffer)) 
	{
		// If we get here, it means that
		// there was an error during compression.
		jpeg_destroy_compress(&cinfo);
		fclose(outfile);
#ifdef _DEBUG
		char buffer[1024];
		cinfo.err->format_message((jpeg_common_struct*)&cinfo, buffer);
		AfxMessageBox(buffer);
#endif
		return false;
	}

	/* Now we can initialize the JPEG compression object. */
	jpeg_create_compress(&cinfo);

	m_bSaveFile_FileExists = false;

	if(!bAllowOverwrite)
	{
		if((outfile = fopen(strPath, "rb")) != NULL) 
		{
			fclose(outfile);
			m_bSaveFile_FileExists = true;
			return false;
		}
	}

	if((outfile = fopen(strPath, "wb")) == NULL) 
		return false;

	jpeg_stdio_dest(&cinfo, outfile);

	cinfo.image_width = m_bih.biWidth; 
	cinfo.image_height = m_bih.biHeight;
	cinfo.input_components = 3;		
	cinfo.in_color_space = JCS_RGB; 

	jpeg_set_defaults(&cinfo);

	if(nQuality > -1)
		jpeg_set_quality(&cinfo, nQuality, TRUE);

	jpeg_start_compress(&cinfo, TRUE);

	GdiFlush();

	DWORD dwByteWidth = GetRowSize_Bytes();
	DWORD dwAlignedRowSize = GetAlignedRowSize_Bytes();

	// Array of pointers to JPEG lines, here we process 
	// the image line by line so the array's size is 1.
	ppbBuffer = (*cinfo.mem->alloc_sarray)
			((j_common_ptr) &cinfo, JPOOL_IMAGE, dwByteWidth, 1);

	BYTE* pNextLine = m_pDIBits + GetSize_Bytes() - dwAlignedRowSize;	
	
	switch(m_bih.biBitCount)
	{
	case 32:
		{
		// Compresion from 32 bit DIB section

		while(pNextLine >= m_pDIBits)
		{
			BYTE* pbWriteBuf = *ppbBuffer;

			// swap R and B components, remove A component
			for(BYTE* pbReadBuf = pNextLine; pbReadBuf < pNextLine + dwAlignedRowSize; pbReadBuf += 4, pbWriteBuf +=3)
			{
				*pbWriteBuf = pbReadBuf[2];
				pbWriteBuf[1] = pbReadBuf[1];
				pbWriteBuf[2] = *pbReadBuf;
			}

			jpeg_write_scanlines(&cinfo, ppbBuffer, 1);
			
			pNextLine -= dwAlignedRowSize;			
		}
		break;
		}
	case 24:
		{
		// Compresion from 24 bit DIB section
	
		while(pNextLine >= m_pDIBits)
		{
			BYTE* pbWriteBuf = *ppbBuffer;

			// swap R and B components
			for(BYTE* pbReadBuf = pNextLine; pbReadBuf < pNextLine + dwByteWidth; pbReadBuf += 3, pbWriteBuf += 3)
			{
				*pbWriteBuf = pbReadBuf[2];
				pbWriteBuf[1] = pbReadBuf[1];
				pbWriteBuf[2] = *pbReadBuf;
			}

			jpeg_write_scanlines(&cinfo, ppbBuffer, 1);
			
			pNextLine -= dwAlignedRowSize;			
		}
		break;
		}
	default:
		ASSERT(0);
	}

	jpeg_finish_compress(&cinfo);
	fclose(outfile);
	jpeg_destroy_compress(&cinfo);

	return true;
}

#endif
//////////////////////////////////////////////////////////////////////////////

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United Kingdom United Kingdom
I started programming on 8 bit machines as a teenager, writing my first compiled programming language before I was 16. I went on to study Engineering and Computer Science at Oxford University, getting a first and the University Prize for the best results in Computer Science. Since then I have worked in a variety of roles, involving systems management and development management on a wide variety of platforms. Now I manage a software development company producing CAD software for Windows using C++.

My 3 favourite reference books are: Design Patterns, Gamma et al; The C++ Standard Library, Josuttis; and Computer Graphics, Foley et al.

Outside computers, I am also the drummer in a band, The Unbelievers and we have just released our first album. I am a pretty good juggler and close up magician, and in my more insane past, I have cycled from Spain to Eastern Turkey, and cycled across the Namib desert.

Comments and Discussions