// Dib.cpp: implementation of the CDib class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Dib.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

CDib::CDib() throw()
{
	m_pBmpFileHeader = NULL;
	m_pBmpInfo = NULL;
	m_pBmpInfoHeader = NULL;
	m_pRGBTable = NULL;
	m_pDibBits = NULL;
	m_uiNumColors = 0;
}

CDib::~CDib()
{
	Clear();
}
BOOL CDib::DibLoaded() const throw()
{
	return (m_pBmpInfoHeader!=NULL);
}
CSize CDib::GetDimensions() const throw()
{	
	if (!DibLoaded()) return CSize(0,0);
	return CSize((int)m_pBmpInfoHeader->biWidth,(int)m_pBmpInfoHeader->biHeight);
}

void CDib::Clear() throw()
{
	if(m_pBmpFileHeader != NULL) {
		delete[] m_pBmpFileHeader;
		m_pBmpFileHeader = NULL;
	}
	if(m_pBmpInfo != NULL) {
		delete[] m_pBmpInfo;
		m_pBmpInfo = NULL;
	}
	if(m_pDibBits != NULL) {
		delete[] m_pDibBits;
		m_pDibBits = NULL;
	}
}

void CDib::Load(CString fileName) throw(CImageException) 
{
	CFile file(fileName, CFile::modeRead | CFile::shareDenyNone);
	Load(&file);
}

void CDib::Load(CFile* pFile) throw(CImageException)
{
	Clear();

	m_pBmpFileHeader = new BITMAPFILEHEADER;
	pFile->Read(static_cast<void*>(m_pBmpFileHeader), sizeof(BITMAPFILEHEADER));
	
	if(m_pBmpFileHeader->bfType != 0x4d42) {
		Clear();
		pFile->Close();
		throw CImageException(_T("Failed to load document"));
	} else {
		DWORD dwLength;
		dwLength = m_pBmpFileHeader->bfOffBits - sizeof(BITMAPFILEHEADER);

		m_pBmpInfo = reinterpret_cast<LPBITMAPINFO>(new BYTE[dwLength]);
		pFile->Read(static_cast<void*>(m_pBmpInfo), dwLength);
		m_pBmpInfoHeader = reinterpret_cast<LPBITMAPINFOHEADER>(m_pBmpInfo);

		if((m_pBmpInfoHeader->biClrUsed == 0))
			m_uiNumColors = 1 << m_pBmpInfoHeader->biBitCount;
		else
			m_uiNumColors = m_pBmpInfoHeader->biClrUsed;

		if(m_pBmpInfoHeader->biClrUsed == 0)
			m_pBmpInfoHeader->biClrUsed = m_uiNumColors;
		
		if(m_uiNumColors <= 256)
			m_pRGBTable = reinterpret_cast<RGBQUAD*>(reinterpret_cast<BYTE*>(m_pBmpInfo) + m_pBmpInfoHeader->biSize);

		unsigned int dwDibLength = (unsigned int)pFile->GetLength() - m_pBmpFileHeader->bfOffBits / 4;
		m_pDibBits = new BYTE[dwDibLength];
		pFile->Read(static_cast<void*>(m_pDibBits), dwDibLength);
	}
}

void CDib::Create(CBitmap* pBitmap) throw(CImageException)
{
	BITMAP Bitmap;
	int rc = pBitmap->GetBitmap(&Bitmap);
	if(rc == 0)
		throw CImageException(_T("Can't get BITMAP from CBitmap*"));

	int iWidthBytes = ((Bitmap.bmWidth * Bitmap.bmBitsPixel + 15) & ~15) >> 3 ;
	DWORD dwImgSize = iWidthBytes * Bitmap.bmHeight;
	
	// File header
	m_pBmpFileHeader = new BITMAPFILEHEADER;
	m_pBmpFileHeader->bfType = 0x4d42;
	m_pBmpFileHeader->bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFO) + dwImgSize; 
	m_pBmpFileHeader->bfReserved1 = 0; 
	m_pBmpFileHeader->bfReserved2 = 0; 
	m_pBmpFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFO) + sizeof(RGBQUAD);
	
	// Bitmap Info header - no support for palette mode
	m_pBmpInfo = new BITMAPINFO;
	m_pBmpInfoHeader = reinterpret_cast<LPBITMAPINFOHEADER>(m_pBmpInfo);
	::ZeroMemory(m_pBmpInfoHeader, sizeof(BITMAPINFOHEADER));
	m_pBmpInfoHeader->biSize = sizeof(BITMAPINFOHEADER);
	m_pBmpInfoHeader->biWidth = Bitmap.bmWidth;
	m_pBmpInfoHeader->biHeight = Bitmap.bmHeight;
	m_pBmpInfoHeader->biPlanes = 1;
	m_pBmpInfoHeader->biBitCount = Bitmap.bmPlanes * Bitmap.bmBitsPixel;
	m_pBmpInfoHeader->biSizeImage = dwImgSize;

	// Color Table
	if((m_pBmpInfoHeader->biClrUsed == 0))
		m_uiNumColors = 1 << (m_pBmpInfoHeader->biBitCount-1);
	else
		m_uiNumColors = m_pBmpInfoHeader->biClrUsed;
	if(m_pBmpInfoHeader->biClrUsed == 0)
		m_pBmpInfoHeader->biClrUsed = m_uiNumColors;
	if(m_uiNumColors <= 256) {
		Clear();
		throw CImageException("Palette mode is not supported");
	}
	// Data bits
	m_pDibBits = new BYTE[dwImgSize];
	pBitmap->GetBitmapBits(dwImgSize, m_pDibBits);
	LPBYTE pTemp = new BYTE[iWidthBytes];
	for(int i=0; i<Bitmap.bmHeight/2; i++) {
		::memcpy(pTemp, m_pDibBits+i*iWidthBytes, iWidthBytes);
		::memcpy(m_pDibBits+i*iWidthBytes, m_pDibBits+iWidthBytes*(Bitmap.bmHeight-1-i), iWidthBytes);
		::memcpy(m_pDibBits+iWidthBytes*(Bitmap.bmHeight-1-i), pTemp, iWidthBytes);
	}
	delete[] pTemp;
}

void CDib::Create(int width, int height, LPBYTE pData, int bitCount) throw(CImageException)
{
	ASSERT(pData);
	ASSERT(bitCount > 8);

	Clear();

	int iWidthBytes = ((width * bitCount + 15) & ~15) >> 3 ;
	DWORD dwImgSize = iWidthBytes * height;
	
	// File header
	m_pBmpFileHeader = new BITMAPFILEHEADER;
	m_pBmpFileHeader->bfType = 0x4d42;
	m_pBmpFileHeader->bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFO) + dwImgSize; 
	m_pBmpFileHeader->bfReserved1 = 0; 
	m_pBmpFileHeader->bfReserved2 = 0; 
	m_pBmpFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFO) + sizeof(RGBQUAD);

	// Bitmap Info header - no support for palette mode
	m_pBmpInfo = new BITMAPINFO;
	m_pBmpInfoHeader = reinterpret_cast<LPBITMAPINFOHEADER>(m_pBmpInfo);
	::ZeroMemory(m_pBmpInfoHeader, sizeof(BITMAPINFOHEADER));
	m_pBmpInfoHeader->biSize = sizeof(BITMAPINFOHEADER);
	m_pBmpInfoHeader->biWidth = width;
	m_pBmpInfoHeader->biHeight = height;
	m_pBmpInfoHeader->biPlanes = 1;
	m_pBmpInfoHeader->biBitCount = bitCount;
	m_pBmpInfoHeader->biSizeImage = dwImgSize;

	// Color Table
	if((m_pBmpInfoHeader->biClrUsed == 0))
		m_uiNumColors = 1 << m_pBmpInfoHeader->biBitCount;
	else
		m_uiNumColors = m_pBmpInfoHeader->biClrUsed;
	if(m_pBmpInfoHeader->biClrUsed == 0)
		m_pBmpInfoHeader->biClrUsed = m_uiNumColors;
	
	if(m_uiNumColors <= 256) {
		Clear();
		throw CImageException("Palette mode is not supported");
	}
	// data bits
	m_pDibBits = pData;		
}

void CDib::Save(CString fileName) throw(CImageException) 
{
	CFile file;
	if(file.Open(fileName, CFile::modeWrite | CFile::modeCreate) == 0)
		throw CImageException(_T("Cannot open a file"));
	Save(&file);
}

void CDib::Save(CFile* pFile) throw(CImageException)
{
	pFile->Write(static_cast<void*>(m_pBmpFileHeader), sizeof(BITMAPFILEHEADER));
	DWORD dwLength;
	if(m_uiNumColors <= 256)
		dwLength = sizeof(BITMAPINFOHEADER) + m_uiNumColors*sizeof(RGBQUAD);
	else
		dwLength = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD);
	pFile->Write(static_cast<void*>(m_pBmpInfo), dwLength);

	DWORD dwImgSize = (((GetWidth() * m_pBmpInfoHeader->biBitCount + 15) & ~15) >> 3) * GetHeight() ;
	pFile->Write(static_cast<void*>(m_pDibBits), dwImgSize);
}

void CDib::Draw(CDC* pdc, int x, int y, double scale, DWORD dwROPCode) const throw(CImageException) {
	if (!this->DibLoaded() ) return;
	if(!m_pBmpInfo)
		throw CImageException();
	int nScanLines = ::StretchDIBits(pdc->GetSafeHdc(), x, y, (int)(GetWidth()*scale) , (int)(GetHeight()*scale), 0, 0, GetWidth(), GetHeight(), 
									m_pDibBits, m_pBmpInfo, DIB_RGB_COLORS, dwROPCode);
	if(nScanLines == GDI_ERROR)
		throw CImageException(_T("CDib::Draw exception"));
}

void CDib::Draw(CDC* pdc, int x, int y, int width, int height, DWORD dwROPCode) const throw(CImageException) {
	if(!m_pBmpInfo)
		throw CImageException();
	int nScanLines = ::StretchDIBits(pdc->GetSafeHdc(), x, y, width , height, 0, 0, GetWidth(), GetHeight(), 
									m_pDibBits, m_pBmpInfo, DIB_RGB_COLORS, dwROPCode);
	if(nScanLines == GDI_ERROR)
		throw CImageException(_T("CDib::Draw exception"));
}

COLORREF CDib::GetPixel(int x, int y) const throw() {
	int width = GetWidth();
	int height = GetHeight();

	BYTE* pDibBits = m_pDibBits;
	BYTE Red;
	BYTE Green;
	BYTE Blue;
	int offset;

	SHORT* psDibBits; // for 16 bit
	BYTE Index;
	RGBQUAD* prgb;

	int bpp = GetBPP();
	switch(bpp) {
	case 1:
	case 2:
	case 4:
	case 8:
		offset = width * (height - 1 - y) + x + 1;
		pDibBits += offset;
		Index = *pDibBits;
		prgb = m_pRGBTable + Index;
		Red = prgb->rgbRed;
		Green = prgb->rgbGreen;
		Blue = prgb->rgbBlue;
		break;
	case 16: // assuming 565
		offset = width * 2 * (height - 1 - y) + (x + 1) * 2;
		pDibBits += offset;
		psDibBits = (SHORT*)pDibBits;
		Blue = *psDibBits >> 11;
		Green = (*psDibBits >> 5) & 63;
		Red = *psDibBits & 127;
		break;
	case 24:
		offset = width * 3 * (height -1 - y) + (x + 1) * 3;
		pDibBits += offset;
		Blue = *pDibBits;
		++pDibBits;
		Green = *pDibBits;
		++pDibBits;
		Red = *pDibBits;
		break;
	case 32:
		offset = width * 4 * (height -1 - y) + (x + 1) * 4;
		pDibBits += offset;
		Blue = *pDibBits;
		++pDibBits;
		Green = *pDibBits;
		++pDibBits;
		Red = *pDibBits;
		break;
	default:
		break;
	}

	return RGB(Red, Green, Blue);
}

void CDib::SetPixel(int x, int y, COLORREF col) throw() {
	int width = GetWidth();
	int height = GetHeight();

	BYTE* pDibBits = m_pDibBits;
	int offset;

	int bpp = GetBPP();
	switch(bpp) {
	case 1:
	case 2:
	case 4:
	case 8:
	case 16:
		break; // no support for these modes... yet.
	case 24:
		offset = width * 3 * (height -1 - y) + (x + 1) * 3;
		pDibBits += offset;
		*pDibBits = GetBValue(col);
		++pDibBits;
		*pDibBits = GetGValue(col);
		++pDibBits;
		*pDibBits = GetRValue(col);
		break;
	case 32:
		offset = width * 4 * (height -1 - y) + (x + 1) * 4;
		pDibBits += offset;
		*pDibBits = GetBValue(col);
		++pDibBits;
		*pDibBits = GetGValue(col);
		++pDibBits;
		*pDibBits = GetRValue(col);
		break;
	default:
		break;
	}
}