// EditImgDlg.cpp : インプリメンテーション ファイル
//

#include "stdafx.h"
#include "DM2GDED.h"
#include "EditImgDlg.h"
#include "CompatDC.h"
#include "EditImageFragsDlg.h"
#include <math.h>

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

namespace
{

void MakeFrom(RGBQUAD &r4, COLORREF clr)
{
	r4.rgbBlue = GetBValue(clr);
	r4.rgbGreen = GetGValue(clr);
	r4.rgbRed = GetRValue(clr);
	r4.rgbReserved = 0;
}

};

/////////////////////////////////////////////////////////////////////////////
// CEditImgDlg::MemDestIo

class CEditImgDlg::MemDestIo
{
	// 
	BYTE *pbBegin, *pbPos, *pbEnd;

public:
	// 
	MemDestIo(BYTE *pbBegin, BYTE *pbEnd): pbBegin(pbBegin), pbPos(pbBegin), pbEnd(pbEnd)
	{

	}
	// 
	UINT GetPosition() const { return pbPos - pbBegin; }
	// 
	bool WriteByte(BYTE x)
	{
		if (pbPos < pbEnd) {
			*pbPos = x;
			pbPos++;
			return true;
		}
		return false;
	}
	// 
	bool Write(const void *pv, UINT cb)
	{
		if (pbPos + cb <= pbEnd) {
			memcpy(pbPos, pv, cb);
			pbPos += cb;
			return true;
		}
		return false;
	}
	// 
	void Rewind()
	{
		pbPos = pbBegin;
	}

};

/////////////////////////////////////////////////////////////////////////////
// CEditImgDlg::MemDestIoL2R4Bit

class CEditImgDlg::MemDestIoL2R4Bit
{
	// 
	MemDestIo& io;
	// 
	BYTE nVal;
	// 
	bool fOdd;

public:
	// 
	MemDestIoL2R4Bit(MemDestIo &io): io(io), fOdd(false) { }
	// 
	bool Write4Bit(BYTE x)
	{
		x = x & 0x0F;
		if (fOdd) {
			nVal = nVal | x;
			fOdd = false;
			if (!io.WriteByte(nVal))
				return false;
		} else {
			nVal = x << 4;
			fOdd = true;
		}
		return true;
	}
	// 
	bool Flush()
	{
		if (fOdd) {
			if (!io.WriteByte(nVal))
				return false;
			fOdd = false;
		}
		return true;
	}

};

/////////////////////////////////////////////////////////////////////////////
// CEditImgDlg::DMExtDM1Wio

class CEditImgDlg::DMExtDM1Wio : public CEditImgDlg::MemDestIoL2R4Bit
{
public:
	// 
	DMExtDM1Wio(MemDestIo& io): MemDestIoL2R4Bit(io)
	{

	}
	// 
	bool WritePixels(BYTE iPixel, UINT nDistance)
	{
		// 0x00 - 0x05 : Most Used 6
		// 0x06 - 0x15 : Selected 16 Colors
		// 0x16        : Empty Colour
		BYTE nFlag = (nDistance > 1) ? 0x8 : 0x0;
		switch (iPixel) {
		case 0x0:
		case 0x1:
		case 0x2:
		case 0x3:
		case 0x4:
		case 0x5:
			if (!Write4Bit(nFlag | iPixel))
				return false;
			break;
		case 0x06:
		case 0x07:
		case 0x08:
		case 0x09:
		case 0x0A:
		case 0x0B:
		case 0x0C:
		case 0x0D:
		case 0x0E:
		case 0x0F:
		case 0x10:
		case 0x11:
		case 0x12:
		case 0x13:
		case 0x14:
		case 0x15:
			if (!Write4Bit(nFlag | 0x7))
				return false;
			if (!Write4Bit(iPixel - 6))
				return false;
			break;
		case 0x16:
			if (!Write4Bit(nFlag | 0x6))
				return false;
			break;
		}
		if (nFlag == 0x8) {
			if (!WriteDistance(nDistance))
				return false;
		}
		return true;
	}
	// 
	bool WriteDistance(UINT nDistance)
	{
		if (nDistance < 2) {
			ASSERT(FALSE);
			return false;
		} else if (nDistance <= 16) {
			if (!Write4Bit(nDistance - 2))
				return false;
		} else if (nDistance < 272) {
			if (false
				|| !Write4Bit(0xF)
				|| !Write4Bit((nDistance - 17) / 16)
				|| !Write4Bit((nDistance - 17) % 16)
			) {
				return false;
			}
		} else if (nDistance < 65536) {
			if (false
				|| !Write4Bit(0xF)
				|| !Write4Bit(0xF)
				|| !Write4Bit(0xF)
				|| !Write4Bit((nDistance >>12) & 0xF)
				|| !Write4Bit((nDistance >> 8) & 0xF)
				|| !Write4Bit((nDistance >> 4) & 0xF)
				|| !Write4Bit((nDistance >> 0) & 0xF)
			) {
				return false;
			}
		} else {
			ASSERT(FALSE);
			return false;
		}
		return true;
	}
};

/////////////////////////////////////////////////////////////////////////////
// CEditImgDlg::C8CH

class CEditImgDlg::C8CH
{
public:
	// 
	bool fIsPixel;
	// 
	BYTE iPixel;
	// 
	UINT nOffset, nLength;

	// 
	C8CH() { }
	// 
	C8CH(BYTE iPixel): fIsPixel(true), iPixel(iPixel)
	{

	}
	// 
	C8CH(UINT nOffset, UINT nLength): fIsPixel(false), nOffset(nOffset), nLength(nLength)
	{

	}
};

/////////////////////////////////////////////////////////////////////////////
// DMIECiIMG4

class DMIECiIMG4
{
public:
	// 
	BYTE pix[200][320];
	// 
	UINT cx, cy;
	// 
	UINT clrs;
	// 
	UINT scatter[256];

	// 
	int Init(CExtBM &bm);
	// 
	BYTE GetPixel(UINT x, UINT y)
	{
		return pix[y][x];
	}

};

int DMIECiIMG4::Init(CExtBM &hbm)
{
	CCompatDC dc;
	HGDIOBJ hOldBM = SelectObject(dc, hbm.m_hObject);
	BITMAP bm;
	hbm.GetBitmap(bm);
	UINT cx = bm.bmWidth;
	UINT cy = bm.bmHeight;
	if ((this->cx = cx) > 320 || (this->cy = cy) > 200)
		return -1;
	bool fClrUsed[256];
	UINT i;
	for (i = 0; i < 256; i++) {
		scatter[i] = 0;
		fClrUsed[i] = false;
	}
	UINT oz = -1;
	for (UINT y = 0; y < cy; y++) {
		for (UINT x = 0; x < cx; x++) {
			COLORREF clr = dc.GetPixel(x, y);
			for (UINT z = 0; z < 256; z++) {
				if (g_DM2Pal[z] == clr)
					break;
			}
			if (z == 256)
				return 0;
			if (z != oz) {
				scatter[z]++;
				oz = z;
			}
			pix[y][x] = z;
			fClrUsed[z] = true;
		}
	}
	clrs = 0;
	for (i = 0; i < 256; i++)
		if (fClrUsed[i])
			clrs++;
	return 1;
}

/////////////////////////////////////////////////////////////////////////////
// DMIECiIMG8

class DMIECiIMG8
{
public:
	// 
	BYTE pix[200][320];
	// 
	UINT cx, cy;

	// 
	int Init(CExtBM &bm);
	// 
	BYTE GetPixel(UINT x, UINT y)
	{
		return pix[y][x];
	}

};

int DMIECiIMG8::Init(CExtBM &hbm)
{
	CCompatDC dc;
	HGDIOBJ hOldBM = SelectObject(dc, hbm.m_hObject);
	BITMAP bm;
	hbm.GetBitmap(bm);
	UINT cx = bm.bmWidth;
	UINT cy = bm.bmHeight;
	if ((this->cx = cx) > 320 || (this->cy = cy) > 200)
		return -1;
	for (UINT y = 0; y < cy; y++) {
		for (UINT x = 0; x < cx; x++) {
			COLORREF clr = dc.GetPixel(x, y);
			for (UINT z = 0; z < 256; z++) {
				if (g_DM2Pal[z] == clr)
					break;
			}
			if (z == 256)
				return 0;
			pix[y][x] = z;
}
	}
	return 1;
}

/////////////////////////////////////////////////////////////////////////////
// CEditImgDlg ダイアログ

CEditImgDlg::CEditImgDlg(BOOL fNo8bpp, BOOL fLE, CWnd* pParent /*=NULL*/)
	: CDialog(CEditImgDlg::IDD, pParent)
	, m_fNo8bpp(fNo8bpp)
	, m_fLE(fLE)
{
	//{{AFX_DATA_INIT(CEditImgDlg)
	m_strEncState = _T("");
	m_strImgTyp = _T("");
	//}}AFX_DATA_INIT

	m_strEncState.LoadString(IDS_STR_ENCHINT_NO);
}

void CEditImgDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CEditImgDlg)
	DDX_Control(pDX, IDC_BUTTON_NORM8, m_wndNorm8);
	DDX_Control(pDX, IDC_STATIC_ORG_PAL, m_wndOrgPal);
	DDX_Control(pDX, IDC_BUTTON_ENCU8, m_wndEncU8);
	DDX_Control(pDX, IDC_BUTTON_REENC4, m_wndReEnc4);
	DDX_Control(pDX, IDC_BUTTON_ENC8, m_wndEnc8);
	DDX_Control(pDX, IDOK, m_wndOk);
	DDX_Control(pDX, IDC_STATIC_PREVIEW, m_wndPreview);
	DDX_Text(pDX, IDC_EDIT_ENC_STATE, m_strEncState);
	DDX_Text(pDX, IDC_EDIT_IMGTYP, m_strImgTyp);
	DDX_Control(pDX, IDC_STATIC_NEW_PAL, m_wndNewPal);
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CEditImgDlg, CDialog)
	//{{AFX_MSG_MAP(CEditImgDlg)
	ON_BN_CLICKED(IDC_BUTTON_COPY, OnButtonCopy)
	ON_BN_CLICKED(IDC_BUTTON_PASTE, OnButtonPaste)
	ON_BN_CLICKED(IDC_BUTTON_ENC4, OnButtonEnc4)
	ON_BN_CLICKED(IDC_BUTTON_ENC8, OnButtonEnc8)
	ON_BN_CLICKED(IDC_BUTTON_REENC4, OnButtonReEnc4)
	ON_BN_CLICKED(IDC_BUTTON_ENCU4, OnButtonEncU4)
	ON_BN_CLICKED(IDC_BUTTON_ENCU8, OnButtonEncU8)
	ON_BN_CLICKED(IDC_BUTTON_EDIT_MORE, OnButtonEditMore)
	ON_BN_CLICKED(IDC_BUTTON_NORM4, OnButtonNorm4)
	ON_BN_CLICKED(IDC_BUTTON_NORM8, OnButtonNorm8)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CEditImgDlg メッセージ ハンドラ

void CEditImgDlg::Init()
{
	m_bm.DeleteObject();

	m_fDisable4 = TRUE;
	m_iImgTyp = typeNo;
	m_fcx = m_fcy = 0;
}

void CEditImgDlg::Init(HBITMAP hBM, DMGHLiIMGM &imgm)
{
	m_bm.DeleteObject();
	m_bm.Attach(hBM);

	if (imgm.fIs8bpp) {
		m_fDisable4 = FALSE;
		m_iImgTyp = typeNo;
		m_fcx = imgm.fcx;
		m_fcy = imgm.fcy;
	} else {
		for (UINT i = 0; i < 16; i++) {
			m_wndOrgPal.SetItem(i, imgm.cPal.p.f.index[i], imgm.cPal.p.f.color[i]);
		}

		m_fDisable4 = TRUE;
		m_iImgTyp = typeNo;
		m_fcx = imgm.fcx;
		m_fcy = imgm.fcy;
	}
}

BOOL CEditImgDlg::OnInitDialog() 
{
	CDialog::OnInitDialog();
	
	m_wndPreview.SetBitmap(m_bm);

	UpdateData(FALSE);

	m_fOrg4bpp = !m_fDisable4;

	RevertState();
	
	return TRUE;  // コントロールにフォーカスを設定しないとき、戻り値は TRUE となります
	              // 例外: OCX プロパティ ページの戻り値は FALSE となります
}

void CEditImgDlg::RevertState()
{
	if (!UpdateData()) return;

	switch (m_iImgTyp) {
	case typeNo: m_strImgTyp.LoadString(IDS_STR_ENC_NO); break;
	case typeC4: m_strImgTyp.LoadString(IDS_STR_ENC_C4); break;
	case typeC8: m_strImgTyp.LoadString(IDS_STR_ENC_C8); break;
	case typeU4: m_strImgTyp.LoadString(IDS_STR_ENC_U4); break;
	case typeU8: m_strImgTyp.LoadString(IDS_STR_ENC_U8); break;
	}

	bool fDisp4bpp = !m_fDisable4;
	bool fEncDone = (m_fvNew.GetSize() > 0);
	bool fEnc4bppDone = (fDisp4bpp && fEncDone);

	m_wndOrgPal.EnableWindow(m_fOrg4bpp);
	m_wndOrgPal.SetReadOnly(TRUE);
	m_wndNewPal.EnableWindow(fEnc4bppDone);
	m_wndNewPal.SetReadOnly(!fEnc4bppDone);

	m_wndEnc8.EnableWindow(!m_fNo8bpp);
	m_wndEncU8.EnableWindow(!m_fNo8bpp);
	m_wndNorm8.EnableWindow(!m_fNo8bpp);

	m_wndOk.EnableWindow(fEncDone);
	m_wndReEnc4.EnableWindow(fEnc4bppDone);

	if (fEncDone) {
		if (m_fDisable4) {
			m_strEncState.FormatMessage(IDS_STR_ENCHINT_C8, m_fvNew.GetSize());
		} else {
			m_strEncState.FormatMessage(IDS_STR_ENCHINT_C4, m_fvNew.GetSize());
		}
	} else {
		m_strEncState.LoadString(IDS_STR_ENCHINT_NO);
	}

	UpdateData(FALSE);
}

void CEditImgDlg::OnButtonCopy() 
{
	HGLOBAL hMem = 0;
	if (m_bm.SaveTo(hMem)) {
		if (OpenClipboard()) {
			EmptyClipboard();
			if (SetClipboardData(CF_DIB, hMem))
				hMem = NULL;
			CloseClipboard();
		}
		if (hMem)
			GlobalFree(hMem);
	}
}

void CEditImgDlg::OnButtonPaste() 
{
	if (OpenClipboard()) {
		HGLOBAL hMem = GetClipboardData(CF_DIB);
		if (hMem) {
			m_bm.DeleteObject();
			m_bm.LoadFrom(hMem);

			m_wndPreview.SetBitmap(m_bm);
		}
		CloseClipboard();
	}
}

void CEditImgDlg::OnButtonEnc4() 
{
	CWaitCursor wc;

	if (!Enc4(NULL)) {
		OnEncFailed();
	}
}

bool CEditImgDlg::EncU4(BYTE pbLocPal16[16])
{
	DMIECiIMG4 ei4;
	int r = ei4.Init(m_bm);
	if (r < 0) {
		AfxMessageBox(IDS_ERR_CVTIMG4_GEN, MB_ICONEXCLAMATION);
		return false;
	}
	if (r == 0) {
		AfxMessageBox(IDS_ERR_CVTIMG4_BAD_PAL, MB_ICONEXCLAMATION);
		return false;
	}
	if (ei4.clrs > 16) {
		AfxMessageBox(IDS_ERR_CVTIMG4_TOO_MANY_CLR, MB_ICONEXCLAMATION);
		return false;
	}
	BYTE bVuff[65536];
	MemDestIo io(bVuff, bVuff + 65536);

	BYTE bPrefix[10] = {0,0,0,0,0,0,0,0,0,0};
	BYTE bSuffix[16];

	UINT cx = ei4.cx;
	UINT cy = ei4.cy;

	typedef multimap<UI32, BYTE, greater<UI32> > Cnt2IdxMMap;
	Cnt2IdxMMap mmap;
	UINT iPal;
	for (iPal = 0; iPal < 256; iPal++) {
		mmap.insert(Cnt2IdxMMap::value_type(ei4.scatter[iPal], (BYTE)iPal));
	}

	BYTE bMostUsed6[6];
	Cnt2IdxMMap::iterator iterPos;
	for (iPal = 0, iterPos = mmap.begin(); iPal < 6; iPal++, iterPos++) {
		bMostUsed6[iPal] = iterPos->second;
	}

	BYTE bLocPal[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
	if (pbLocPal16) {
		memcpy(bLocPal, pbLocPal16, 16);
	} else {
		int iFreeIdx = -1;
		UINT iIdx = 0;
		for (iPal = 0; iPal < 256; iPal++) {
			if (ei4.scatter[iPal] != 0) {
				bLocPal[iIdx] = iPal;
				iIdx++;
			} else if (iFreeIdx < 0) {
				iFreeIdx = iIdx;
			}
		}
		for (; iIdx < 16; iIdx++) {
			bLocPal[iIdx] = iFreeIdx;
		}
	}

	BYTE bPixMap[256];
	for (iPal = 0; iPal < 256; iPal++) {
		UINT iIdx;
		for (iIdx = 0; iIdx < 16; iIdx++) {
			if (iPal == bLocPal[iIdx])
				break;
		}
		if (iIdx == 16) {
			bPixMap[iPal] = 255;
		} else {
			bPixMap[iPal] = iIdx;
		}
	}

	bPrefix[0] = m_fLE ? LOBYTE(cx) : HIBYTE(cx);
	bPrefix[1] = m_fLE ? HIBYTE(cx) : LOBYTE(cx);
	bPrefix[2] = m_fLE ? LOBYTE(cy) : HIBYTE(cy);
	bPrefix[3] = m_fLE ? HIBYTE(cy) : LOBYTE(cy);
	bPrefix[4] = 4;
	bPrefix[5] = 0;
	bPrefix[6] = bPrefix[0];
	bPrefix[7] = bPrefix[1];
	bPrefix[8] = bPrefix[2];
	bPrefix[9] = bPrefix[3];
	bPrefix[m_fLE ? 1 : 0] |= 0x80 | GetFactorX();
	bPrefix[m_fLE ? 3 : 2] |= 0x80 | GetFactorY();
	io.Write(bPrefix, 10);
	//
	for (UINT y = 0; y < cy; y++) {
		for (UINT x = 0; x < cx; x += 2) {
			BYTE bPix[2] = {
				bPixMap[ei4.GetPixel(x  , y)],
				bPixMap[ei4.GetPixel(x+1, y)],
			};
			ASSERT(bPix[0] != -1 && bPix[1] != -1);
			BYTE iPix = 0
				| (bPix[0] << 4)
				| (bPix[1] << 0)
				;
			// MUST be byte aligned for odd width, and should be done
			if (!io.WriteByte(iPix))
				return false;
		}
	}
	//
	memcpy(bSuffix, bLocPal, 16);
	if (!io.Write(bSuffix, 16))
		return false;

	if (m_fvNew.Alloc(io.GetPosition(), bVuff)) {
		for (UINT i = 0; i < 16; i++) {
			m_wndNewPal.SetItem(i, bSuffix[i], g_DM2Pal[bSuffix[i]]);
			m_wndNewPal.Invalidate();
		}
		m_fDisable4 = FALSE;
		m_iImgTyp = typeU4;

		RevertState();
	}

	return true;
}

bool CEditImgDlg::Enc4(BYTE pbLocPal16[16])
{
	DMIECiIMG4 ei4;
	int r = ei4.Init(m_bm);
	if (r < 0) {
		AfxMessageBox(IDS_ERR_CVTIMG4_GEN, MB_ICONEXCLAMATION);
		return false;
	}
	if (r == 0) {
		AfxMessageBox(IDS_ERR_CVTIMG4_BAD_PAL, MB_ICONEXCLAMATION);
		return false;
	}
	if (ei4.clrs > 16) {
		AfxMessageBox(IDS_ERR_CVTIMG4_TOO_MANY_CLR, MB_ICONEXCLAMATION);
		return false;
	}
	BYTE bVuff[65536];
	MemDestIo io(bVuff, bVuff + 65536);

	BYTE bPrefix[7];
	BYTE bSuffix[16];

	UINT cx = ei4.cx;
	UINT cy = ei4.cy;

	typedef multimap<UI32, BYTE, greater<UI32> > Cnt2IdxMMap;
	Cnt2IdxMMap mmap;
	UINT iPal;
	for (iPal = 0; iPal < 256; iPal++) {
		mmap.insert(Cnt2IdxMMap::value_type(ei4.scatter[iPal], (BYTE)iPal));
	}

	BYTE bMostUsed6[6];
	Cnt2IdxMMap::iterator iterPos;
	for (iPal = 0, iterPos = mmap.begin(); iPal < 6; iPal++, iterPos++) {
		bMostUsed6[iPal] = iterPos->second;
	}

	BYTE bLocPal[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
	if (pbLocPal16) {
		memcpy(bLocPal, pbLocPal16, 16);
	} else {
		int iFreeIdx = -1;
		UINT iIdx = 0;
		for (iPal = 0; iPal < 256; iPal++) {
			if (ei4.scatter[iPal] != 0) {
				bLocPal[iIdx] = iPal;
				iIdx++;
			} else if (iFreeIdx < 0) {
				iFreeIdx = iIdx;
			}
		}
		for (; iIdx < 16; iIdx++) {
			bLocPal[iIdx] = iFreeIdx;
		}
	}

	BYTE bMostUsed6Idx[6];
	UINT iRot = 0;
	for (iPal = 0; iPal < 6; iPal++) {
		UINT iIdx;
		for (iIdx = 0; iIdx < 16; iIdx++, iRot++, iRot %= 16) {
			if (bMostUsed6[iPal] == bLocPal[iRot]) {
				bMostUsed6Idx[iPal] = iRot;
				iRot++;
				break;
			}
		}
		if (iIdx == 16) {
			// less than 6 colors in image
			bMostUsed6Idx[iPal] = bMostUsed6Idx[0];
		}
	}

	BYTE bPixMap[256];
	for (iPal = 0; iPal < 256; iPal++) {
		UINT iIdx;
		for (iIdx = 0; iIdx < 6; iIdx++) {
			if (iPal == bMostUsed6[iIdx])
				break;
		}
		if (iIdx == 6) {
			for (iIdx = 0; iIdx < 16; iIdx++) {
				if (iPal == bLocPal[iIdx])
					break;
			}
			if (iIdx == 16) {
				bPixMap[iPal] = 255;
			} else {
				bPixMap[iPal] = 6 + iIdx;
			}
		} else {
			bPixMap[iPal] = iIdx;
		}
	}

	bPrefix[0] = m_fLE ? LOBYTE(cx) : HIBYTE(cx);
	bPrefix[1] = m_fLE ? HIBYTE(cx) : LOBYTE(cx);
	bPrefix[2] = m_fLE ? LOBYTE(cy) : HIBYTE(cy);
	bPrefix[3] = m_fLE ? HIBYTE(cy) : LOBYTE(cy);
	bPrefix[4] = (bMostUsed6Idx[0] << 4) | (bMostUsed6Idx[1]);
	bPrefix[5] = (bMostUsed6Idx[2] << 4) | (bMostUsed6Idx[3]);
	bPrefix[6] = (bMostUsed6Idx[4] << 4) | (bMostUsed6Idx[5]);
	bPrefix[m_fLE ? 1 : 0] |= GetFactorX();
	bPrefix[m_fLE ? 3 : 2] |= GetFactorY();
	io.Write(bPrefix, 7);
	//
	DMExtDM1Wio wio(io);
	UINT iPixel = 0, nPixels = cx * cy;
	while (iPixel < nPixels) {
		BYTE iPix = ei4.GetPixel(iPixel % cx, iPixel / cx);
		int iIdenticalPixel = iPixel + 1;
		for (; iIdenticalPixel < nPixels && iPix == ei4.GetPixel(iIdenticalPixel % cx, iIdenticalPixel / cx); iIdenticalPixel++);
		int iEmptyPixel = iPixel;
		if (cx <= iPixel) {
			for (; iEmptyPixel < nPixels && ei4.GetPixel(iEmptyPixel % cx, iEmptyPixel / cx - 1) == ei4.GetPixel(iEmptyPixel % cx, iEmptyPixel / cx); iEmptyPixel++);
		}
		if (iIdenticalPixel <= iEmptyPixel) {
			iEmptyPixel -= iPixel;
			if (!wio.WritePixels(0x16, iEmptyPixel))
				return false;
			iPixel += iEmptyPixel;
		} else {
			ASSERT(bPixMap[iPix] != 255);
			iIdenticalPixel -= iPixel;
			if (!wio.WritePixels(bPixMap[iPix], iIdenticalPixel))
				return false;
			iPixel += iIdenticalPixel;
		}
	}
	wio.Flush();
	//
	memcpy(bSuffix, bLocPal, 16);
	if (!io.Write(bSuffix, 16))
		return false;

	if (m_fvNew.Alloc(io.GetPosition(), bVuff)) {
		for (UINT i = 0; i < 16; i++) {
			m_wndNewPal.SetItem(i, bSuffix[i], g_DM2Pal[bSuffix[i]]);
			m_wndNewPal.Invalidate();
		}
		m_fDisable4 = FALSE;
		m_iImgTyp = typeC4;

		RevertState();
	}

	return true;
}

void CEditImgDlg::OnButtonEnc8() 
{
	CWaitCursor wc;

	if (!Enc8()) {
		OnEncFailed();
	}
}

bool CEditImgDlg::EncU8()
{
	DMIECiIMG8 ei8;
	int r = ei8.Init(m_bm);
	if (r < 0) {
		AfxMessageBox(IDS_ERR_CVTIMG8_GEN, MB_ICONEXCLAMATION);
		return false;
	}
	if (r == 0) {
		AfxMessageBox(IDS_ERR_CVTIMG8_BAD_PAL, MB_ICONEXCLAMATION);
		return false;
	}
	BYTE bVuff[65536];
	MemDestIo io(bVuff, bVuff + 65536);

	BYTE bPrefix[10];

	UINT cx = ei8.cx;
	UINT cy = ei8.cy;

	bPrefix[0] = m_fLE ? LOBYTE(cx) : HIBYTE(cx);
	bPrefix[1] = m_fLE ? HIBYTE(cx) : LOBYTE(cx);
	bPrefix[2] = m_fLE ? LOBYTE(cy) : HIBYTE(cy);
	bPrefix[3] = m_fLE ? HIBYTE(cy) : LOBYTE(cy);
	bPrefix[4] = 8;
	bPrefix[5] = 0;
	bPrefix[6] = bPrefix[0];
	bPrefix[7] = bPrefix[1];
	bPrefix[8] = bPrefix[2];
	bPrefix[9] = bPrefix[3];
	bPrefix[m_fLE ? 1 : 0] |=    0 | GetFactorX();
	bPrefix[m_fLE ? 3 : 2] |= 0x80;
	io.Write(bPrefix, 10);
	//
	for (UINT y = 0; y < cy; y++) {
		for (UINT x = 0; x < cx; x ++) {
			BYTE bPix = ei8.GetPixel(x, y);
			if (!io.WriteByte(bPix))
				return false;
		}
	}
	//

	if (m_fvNew.Alloc(io.GetPosition(), bVuff)) {
		m_fDisable4 = TRUE;
		m_iImgTyp = typeU8;

		RevertState();
	}

	return true;
}

bool CEditImgDlg::Enc8()
{
	DMIECiIMG8 ei8;
	int r = ei8.Init(m_bm);
	if (r < 0) {
		AfxMessageBox(IDS_ERR_CVTIMG8_GEN, MB_ICONEXCLAMATION);
		return false;
	}
	if (r == 0) {
		AfxMessageBox(IDS_ERR_CVTIMG8_BAD_PAL, MB_ICONEXCLAMATION);
		return false;
	}
	BYTE bVuff[65536];
	MemDestIo io(bVuff, bVuff + 65536);
	if (Enc8As(ei8, io, true)) {
		UINT cbVuff = io.GetPosition();
		if (!m_fvNew.Alloc(cbVuff, bVuff))
			return false;
		io.Rewind();
		if (Enc8As(ei8, io, false)) {
			UINT cbVuff = io.GetPosition();
			if (cbVuff < m_fvNew.GetSize()) {
				if (!m_fvNew.Alloc(cbVuff, bVuff))
					return false;
			}

			m_fDisable4 = TRUE;
			m_iImgTyp = typeC8;
			RevertState();
			return true;
		}
	}
	return false;
}

bool CEditImgDlg::Enc8As(DMIECiIMG8 &ei8, MemDestIo &io, bool fIsType3)
{
	BYTE bPrefix[8];

	UINT cx = ei8.cx & 0x3FF;
	UINT cy = ei8.cy & 0x3FF;

	bPrefix[0] = m_fLE ? LOBYTE(cx) : HIBYTE(cx);
	bPrefix[1] = m_fLE ? HIBYTE(cx) : LOBYTE(cx);
	bPrefix[2] = m_fLE ? LOBYTE(cy) : HIBYTE(cy);
	bPrefix[3] = m_fLE ? HIBYTE(cx) : LOBYTE(cy);
	bPrefix[4] = 0;
	bPrefix[5] = 0;
	bPrefix[6] = fIsType3 ? 3 : 2;
	bPrefix[7] = 0;

	bPrefix[m_fLE ? 1 : 0] |= 0x00|GetFactorX();
	bPrefix[m_fLE ? 3 : 2] |= 0x7C;

	// Type3: XXXX XXXX XXXY YYYY {X: 11-bits (0-2047), Y: 5-bits (0-31)}
	// Type2: XXXX XXXX XXXX YYYY {X: 12-bits (0-4095), Y: 4-bits (0-15)}

	io.Write(bPrefix, 8);
	//
	UINT iPixel = 0, nPixels = cx * cy;
	BYTE iPix;
	C8CH aCmds[8];
	UINT iCmd = 0;
	const UINT nMaxLength = fIsType3 ? (31 + 3) : (15 + 3);
	const UINT nMaxOffset = fIsType3 ? 2047 : 4095;

	while (iPixel < nPixels) {
		UINT iMinLength = 3;
		UINT iMaxLength = __min(nMaxLength, nPixels - iPixel);
		UINT iMinOffset = 1;
		UINT iMaxOffset = __min(nMaxOffset, iPixel);

		UINT iBestOffset = 0;
		UINT iBestLength = 0;

		UINT iChkOffset;
		for (iChkOffset = iMinOffset; iChkOffset <= iMaxOffset; iChkOffset++)
		{
			int iOffset;
			for (iOffset = 0; iOffset < iMaxLength; iOffset++)
			{
				int iOffset1 = iPixel + iOffset;
				int iOffset2 = iPixel + iOffset - iChkOffset;
				if (ei8.GetPixel(iOffset1 % cx, iOffset1 / cx) != ei8.GetPixel(iOffset2 % cx, iOffset2 / cx))
					break;
			}
			if (iMinLength <= iOffset && iBestLength < iOffset)
			{
				iBestOffset = iChkOffset;
				iBestLength = iOffset;

				if (iBestLength == nMaxLength) break;
			}
		}
		if (iBestLength == 0) {
			iPix = ei8.GetPixel(iPixel % cx, iPixel / cx);

			aCmds[iCmd] = C8CH(iPix);
			iCmd++;
			iPixel++;
		} else {
			aCmds[iCmd] = C8CH(iBestOffset, iBestLength);
			iCmd++;
			iPixel += iBestLength;
		}

		if (iCmd == 8 || iPixel == nPixels)
		{
			int nCmds = iCmd;
			BYTE bCmdCtrl = 0;
			static BYTE bTbl8[8] = {1,2,4,8,16,32,64,128};
			for (iCmd = 0; iCmd < nCmds; iCmd++)
				bCmdCtrl |= aCmds[iCmd].fIsPixel ? bTbl8[iCmd] : 0;
			if (!io.WriteByte(bCmdCtrl))
				return false;
			for (iCmd = 0; iCmd < nCmds; iCmd++)
			{
				if (aCmds[iCmd].fIsPixel)
				{
					if (!io.WriteByte(aCmds[iCmd].iPixel))
						return false;
				}
				else
				{
					BYTE v1, v2;
					if (fIsType3)
					{
						v1 = ((aCmds[iCmd].nOffset & 0x0007) << 5)|(aCmds[iCmd].nLength - 3);
						v2 = ((aCmds[iCmd].nOffset & 0xFFF8) >> 3);
					}
					else
					{
						v1 = ((aCmds[iCmd].nOffset & 0x000F) << 4)|(aCmds[iCmd].nLength - 3);
						v2 = ((aCmds[iCmd].nOffset & 0xFFF0) >> 4);
					}
					if (false
						|| !io.WriteByte(v1)
						|| !io.WriteByte(v2)
					) {
						return false;
					}
				}
			}
			iCmd = 0;
		}
	}
	//
	return true;
}

void CEditImgDlg::OnButtonReEnc4() 
{
	CWaitCursor wc;

	BYTE bLocPal[16];
	UINT i;
	for (i = 0; i < 16; i++) {
		COLORREF clr;
		m_wndNewPal.GetItem(i, bLocPal[i], clr);
	}
	switch (m_iImgTyp) {
	case typeC4:
		if (!Enc4(bLocPal)) {
			OnEncFailed();
		}
		break;
	case typeU4:
		if (!EncU4(bLocPal)) {
			OnEncFailed();
		}
		break;
	}
}

void CEditImgDlg::OnEncFailed()
{
	m_fvNew.Free();
	m_fDisable4 = TRUE;
	m_iImgTyp = typeNo;
	RevertState();
}

void CEditImgDlg::OnButtonEncU4() 
{
	CWaitCursor wc;

	if (!EncU4(NULL)) {
		OnEncFailed();
	}
}

void CEditImgDlg::OnButtonEncU8() 
{
	CWaitCursor wc;

	if (!EncU8()) {
		OnEncFailed();
	}
}

void CEditImgDlg::OnButtonEditMore() 
{
	CEditImageFragsDlg wndDlg(m_fcx, m_fcy, this);
	int r = wndDlg.DoModal();
	if (r != IDOK)
		return;
	m_fcx = wndDlg.GetWidthFrags();
	m_fcy = wndDlg.GetHeightFrags();
}

void CEditImgDlg::OnButtonNorm4() 
{
	CWaitCursor wc;

	CExtBM bmNew;
	int r = NormTo8(m_bm, bmNew, FALSE);
	if (r < 0) {
		AfxMessageBox(IDS_ERR_CVTIMG8_GEN, MB_ICONEXCLAMATION);
		return;
	}
	m_bm.DeleteObject();
	m_bm.Attach(bmNew.Detach());

	m_wndPreview.SetBitmap(m_bm);
}

void CEditImgDlg::OnButtonNorm8() 
{
	CWaitCursor wc;

	CExtBM bmNew;
	int r = NormTo8(m_bm, bmNew, TRUE);
	if (r < 0) {
		AfxMessageBox(IDS_ERR_CVTIMG8_GEN, MB_ICONEXCLAMATION);
		return;
	}
	m_bm.DeleteObject();
	m_bm.Attach(bmNew.Detach());

	m_wndPreview.SetBitmap(m_bm);
}

int CEditImgDlg::NormTo8(CExtBM &bmSrc, CExtBM &bmDst, BOOL fTo8)
{
	CCompatDC dc;
	HGDIOBJ hOldBM = SelectObject(dc, bmSrc.m_hObject);
	BITMAP bm;
	bmSrc.GetBitmap(bm);
	UINT cx = bm.bmWidth;
	UINT cy = bm.bmHeight;
	if ((cx) > 320 || (cy) > 200)
		return -1;
	UINT i;
	UINT nUsed[256], nColours = 0;
	for (i = 0; i < 256; i++) {
		nUsed[i] = 0;
	}
	RGBQUAD r4[256];
	UINT iMakePal;
	for (iMakePal = 0; iMakePal < 256; iMakePal++)
		MakeFrom(r4[iMakePal], g_DM2Pal[iMakePal]);
	BYTE bPix[240][320];
	for (UINT y = 0; y < cy; y++) {
		for (UINT x = 0; x < cx; x++) {
			BYTE z = GetNearestPalIdx8(dc.GetPixel(x, y), r4);
			bPix[y][x] = z;
			if (nUsed[z] == 0)
				nColours++;

			nUsed[z]++;
		}
	}
	SelectObject(dc, hOldBM);
	if (!fTo8) {
		typedef multimap<UI32, BYTE, greater<UI32> > Cnt2IdxMMap;
		Cnt2IdxMMap mmap;
		UINT iPal;
		for (iPal = 0; iPal < 256; iPal++)
			mmap.insert(Cnt2IdxMMap::value_type(nUsed[iPal], iPal));
		Cnt2IdxMMap::iterator iterPos = mmap.begin();
		RGBQUAD r4_4[16];
		BYTE bPixMap[16];
		UINT iMakePal;
		for (iMakePal = 0; iMakePal < 16; iMakePal++, iterPos++)
			r4_4[iMakePal] = r4[bPixMap[iMakePal] = iterPos->second];
		for (UINT y = 0; y < cy; y++) {
			for (UINT x = 0; x < cx; x++) {
				BYTE z = GetNearestPalIdx4(g_DM2Pal[bPix[y][x]], r4_4);
				bPix[y][x] = bPixMap[z];
			}
		}
	}
	if (!bmDst.Create(CSize(cx, cy), 8, r4, 256))
		return -1;
	{
		HGDIOBJ hOldBM = SelectObject(dc, bmDst.m_hObject);
		for (UINT y = 0; y < cy; y++) {
			for (UINT x = 0; x < cx; x++) {
				dc.SetPixel(x, y, g_DM2Pal[bPix[y][x]]);
			}
		}
		SelectObject(dc, hOldBM);
	}
	return 1;
}

BYTE CEditImgDlg::GetNearestPalIdx8(COLORREF clr, RGBQUAD r4[256])
{
	UINT z;
	int iR1 = GetRValue(clr);
	int iG1 = GetGValue(clr);
	int iB1 = GetBValue(clr);
	BYTE nBetterIdx = 0;
	UINT nBetterDist = 768;
	for (z = 0; z < 256; z++) {
		int iR0 = r4[z].rgbRed;
		int iG0 = r4[z].rgbGreen;
		int iB0 = r4[z].rgbBlue;

		UINT nDist = abs(iR0 - iR1) + abs(iG0 - iG1) / 2 + abs(iB0 - iB1) / 2;

		if (nDist < nBetterDist) {
			nBetterDist = nDist;
			nBetterIdx = z;
		}
	}
	return nBetterIdx;
}

BYTE CEditImgDlg::GetNearestPalIdx4(COLORREF clr, RGBQUAD r4[16])
{
	UINT z;
	int iR1 = GetRValue(clr);
	int iG1 = GetGValue(clr);
	int iB1 = GetBValue(clr);
	BYTE nBetterIdx = 0;
	UINT nBetterDist = 768;
	for (z = 0; z < 16; z++) {
		int iR0 = r4[z].rgbRed;
		int iG0 = r4[z].rgbGreen;
		int iB0 = r4[z].rgbBlue;

		UINT nDist = abs(iR0 - iR1) + abs(iG0 - iG1) / 2 + abs(iB0 - iB1) / 2;

		if (nDist < nBetterDist) {
			nBetterDist = nDist;
			nBetterIdx = z;
		}
	}
	return nBetterIdx;
}
