www.pudn.com > src.rar > subresyncDlgOCR.cpp


#include "stdafx.h" 
 
#include  
 
#include "subresync.h" 
#include "subresyncDlg.h" 
#include "../vobsub/vobsub.h" 
 
// 
 
class AccumAvg 
{ 
	double* buff, avg; 
	int len, pos, elen; 
 
public: 
 
	AccumAvg(int len) 
	{ 
		this->len = len; 
		buff = new double[len]; 
		pos = -1; 
		elen = 0; 
	} 
 
	~AccumAvg() 
	{ 
		if(buff) delete [] buff; 
	} 
 
	double First(double e) 
	{ 
		if(!buff) return(-1); 
 
		buff[0] = e; 
 
		pos = 0; 
		elen = 1; 
 
		return(avg = e); 
	} 
 
	double Add(double e) 
	{ 
		if(!buff) return(-1); 
 
		pos = (pos + 1) % len; 
 
		if(elen == len) 
		{ 
			avg -= (buff[pos]) / len; 
			avg += (buff[pos] = e) / len; 
			 
		} 
		else 
		{ 
			buff[pos] = e; 
			elen++; 
			avg = 0; 
			for(int i = 0; i < elen; i++) avg += buff[i]; 
			avg /= elen; 
		} 
 
		return(avg); 
	} 
 
	double Avg(double e = 0) 
	{ 
		if(elen == 0) Add(e); 
 
		return(avg); 
	} 
}; 
 
// 
 
static HGLOBAL CreateDIB(RGBQUAD* p, int w, int h) 
{ 
	int pitch = (w + 31) / 32 * 4; 
 
	struct BM {BITMAPINFOHEADER bmiHeader; DWORD pal[2];} b; 
	memset(&b, 0, sizeof(b)); 
	b.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); 
	b.bmiHeader.biWidth = w; 
	b.bmiHeader.biHeight = h; 
	b.bmiHeader.biBitCount = 1; 
	b.bmiHeader.biPlanes = 1; 
	b.bmiHeader.biCompression = BI_RGB; 
	b.bmiHeader.biSizeImage = pitch*h; 
	b.bmiHeader.biClrUsed = b.bmiHeader.biClrImportant = 2; 
	b.pal[0] = 0xFFFFFF; 
	b.pal[1] = 0x000000; 
 
	HGLOBAL hDIB = ::GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, sizeof(b)+b.bmiHeader.biSizeImage); 
    if(hDIB == NULL) 
		return(NULL); 
 
	BYTE* pBase = (BYTE*)::GlobalLock(hDIB); 
 
	memcpy(pBase, &b, sizeof(b)); 
	pBase += sizeof(b); 
 
	for(int j = h-1; j >= 0; j--) 
	{ 
		BYTE* d = &pBase[pitch*j]; 
		memset(d, 0, pitch); 
 
		for(int i = 0, k = 0; i < w; i++, p++) 
		{ 
			k = (~i)&7; 
			*d |= (!!p->rgbReserved) << k; 
			if(!k) d++; 
		} 
	} 
 
	::GlobalUnlock(hDIB); 
 
    return(hDIB); 
} 
 
static CString ocrstr; 
 
static void myOutputHandler(int infotype, int param) 
{ 
	int i = 0; 
 
	switch(infotype) 
	{ 
	case OT_TEXT: 
		ocrstr += (char)param; 
		TRACE(_T("%c"), (TCHAR)param&0xff); 
		break; 
	case OT_ENDL: 
		ocrstr += _T("\n"); 
		TRACE(_T("\n")); 
		break; 
	case OT_ENDZ: 
		ocrstr += _T("\n\n"); 
		TRACE(_T("\n\n")); 
	} 
} 
 
#include "AskCharDlg.h" 
 
bool CSubresyncDlg::DoOCR(CSimpleTextSubtitle& sts, bool fClearImgLetterDb, bool fOcrDll) 
{ 
	if(m_mode != VOBSUB) return(false); 
 
	CArray & sub = m_file.m_subidx[m_file.GetLangIdx()]; 
	if(sub.GetSize() <= 0) return(false); 
 
	m_fOCRing = true; 
 
	if(fClearImgLetterDb) FreeCharImgList(); 
 
	ClearSelectedLines(); 
 
	sts.RemoveAll(); 
#ifdef UNICODE 
	sts.SetStylesToUnicode(true); 
#else 
	sts.SetStylesToUnicode(false); 
#endif 
 
	int dlgret = 0; 
 
	bool fRememberI = false; 
 
	AccumAvg gapavg(100); 
	AccumAvg heightavg(100); 
	AccumAvg widthavg(100); 
 
	heightavg.First(30); 
	gapavg.First(7); 
 
	CString General(MAKEINTRESOURCE(IDS_R_GENERAL)); 
	CString SpaceSensitivity(MAKEINTRESOURCE(IDS_RG_SPACESENS)); 
 
	double spacesens = 1.0 + 1.0*AfxGetApp()->GetProfileInt(General, SpaceSensitivity, 35)/50; 
 
	if(m_od.IsValid()) 
	{ 
		m_od.SetOutputMode(OM_TEXT/*OM_RICHTEXT*/); 
		m_od.OCRSetOutputHandler(myOutputHandler); 
	} 
 
	CWaitCursor wc; 
 
	POSITION savepos = m_savelist.GetHeadPosition(); 
	while(savepos && dlgret != IDCANCEL && m_fOCRing) 
	{ 
		int i = m_savelist.GetNext(savepos); 
 
		if(!m_file.GetFrame(i)) continue; 
 
		m_w = m_file.m_img.rect.Width(); 
		m_h = m_file.m_img.rect.Height(); 
		m_p = (uint*)m_file.m_img.lpPixels; 
 
		if(m_w*m_h <= 0) continue; 
 
		CString str; 
 
		if(fOcrDll && m_od.IsValid()) 
		{ 
			m_file.m_img.ClearBorders(); 
 
			HGLOBAL hDIB = CreateDIB((RGBQUAD*)m_p, m_w, m_h); 
			if(!hDIB) continue; 
 
			IMG* img = m_od.DIBToIMG(hDIB); 
			if(img) 
			{ 
				ocrstr.Empty(); 
   				m_od.OCR(img, 0); 
				m_od.FreeImg(img); 
			} 
 
			::GlobalFree(hDIB); 
 
			str = ocrstr; 
		} 
		else 
		{ 
			int* left = new int[m_h]; 
			if(!left) continue; 
 
			int* right = new int[m_h]; 
			if(!left) {delete [] left; continue;} 
 
			GetVobSubImage(i); // m_file.GetFrame(i) has already cached the subpicture, this call will only create the bitmap for the preview window 
 
			m_file.m_img.ClearBorders(); 
 
			m_srow = m_erow = -1; 
 
			for(int row = 0; row < m_h && dlgret != IDCANCEL; ) 
			{ 
				if(!FindNextFilledRow(row) || row >= (m_h-1)) break; 
				m_srow = row; 
 
				FindNextEmptyRow(row, heightavg.Avg()/2); 
				m_erow = row - 1; 
 
				FreeCharSegmentList(); 
 
				memset(left, 0, m_h*sizeof(int)); 
 
				while(FindNextFilledCol(left)) 
				{ 
					memcpy(right, left, m_h*sizeof(int)); 
 
					FindNextEmptyCol(right); 
 
					CharSegment* cs = new CharSegment(left, right, m_h, m_srow, m_erow); 
					if(cs) m_cslist.AddTail(cs); 
 
					heightavg.Add(ClipChar(cs->left, cs->right).Height()); 
					widthavg.Add(ClipChar(cs->left, cs->right).Width()); 
 
					memcpy(left, right, m_h*sizeof(int)); 
				} 
 
				POSITION pos = m_cslist.GetHeadPosition(); 
				while(pos && dlgret != IDCANCEL) 
				{ 
					POSITION endpos = pos; 
 
					if(pos != m_cslist.GetHeadPosition()) 
					{ 
						POSITION prev = pos; 
						m_cslist.GetPrev(prev); 
 
						double gap = GetGapBetweenTwoChars(m_cslist.GetAt(prev), m_cslist.GetAt(pos)); 
 
						gap = max(gap, 2); 
 
						double avg = gapavg.Avg(gap)*spacesens; 
 
						if(gap < avg + 1) 
							avg = max(avg, heightavg.Avg()/2); 
 
						if(gap > avg) 
							str += ' '; 
						else 
							gapavg.Add(gap); 
					} 
 
					do 
					{ 
						dlgret = IDOK; 
 
						CharSegment* startcs = m_cslist.GetAt(pos); 
						CharSegment* endcs = m_cslist.GetAt(endpos); 
 
						CRect r = ClipChar(startcs->left, endcs->right); 
 
						m_curSegmRect = r; 
 
						CString str2; 
						POSITION newendpos; 
						if(LookupCharImg(str2, pos, endpos, newendpos)) 
						{ 
							str += str2; 
							endpos = newendpos; 
							m_cslist.GetNext(endpos); // TODO 
						} 
						else 
						{ 
							SetSelectedLine(i); 
 
							POSITION tmppos = endpos; 
							m_cslist.GetNext(tmppos); 
 
							double rx = 1.0 * m_subimgrect.Width() / m_w; 
							double ry = 1.0 * m_subimgrect.Height() / m_h; 
 
							CPoint p( 
								m_subimgrect.left + (int)(rx*m_curSegmRect.left), 
								m_subimgrect.top + (int)(ry*m_curSegmRect.top)); 
 
							ClientToScreen(&p); 
 
							InvalidateRect(m_subimgrect); 
 
							CRect tmprect = (m_curSegmRect + CRect(1, 1, 2, 2)) & CRect(0, 0, m_w-1, m_h-1); 
 
							CAskCharDlg dlg(pos != endpos, !!tmppos, p, m_subdc, m_subimg, tmprect, str); 
							dlgret = dlg.DoModal(); 
							 
							if(dlgret == IDOK) 
							{ 
								if(!dlg.m_str.IsEmpty())  
								{ 
									str += dlg.m_str; 
 
									if(dlg.m_str == _T("l")) fRememberI = true; 
									 
									if(!(dlg.m_str == _T("I")) || fRememberI) 
									{ 
										CharImg* img = new CharImg(m_p, m_w, r, startcs->left, endcs->right, TopBottom(r), dlg.m_str); 
										if(img) m_cilist.AddHead(img); 
									} 
								} 
 
								m_cslist.GetNext(endpos); // TODO 
							} 
							else if(dlgret == IDC_EXTEND) 
							{ 
								m_cslist.GetNext(endpos); 
							} 
							else if(dlgret == IDC_UNDOEXTEND) 
							{ 
								m_cslist.GetPrev(endpos); 
							} 
						} 
					} 
					while(dlgret == IDC_EXTEND || dlgret == IDC_UNDOEXTEND); 
 
					// TODO 
	//				m_cslist.GetNext(endpos); 
 
					pos = endpos; 
				} 
 
				FreeCharSegmentList(); 
 
				m_curSegmRect = CRect(0, 0, 0, 0); 
 
				str += _T("\n"); 
			} 
 
			delete [] left; 
			delete [] right; 
		} 
 
		str.TrimRight(); 
 
		CorrectSpellingErrors(str); 
 
		sts.Add(str, m_rts[i].start, m_rts[i].start + m_file.m_img.nDisplayTime); 
	} 
 
	ClearSelectedLines(); 
 
	RearrangeControls(); 
 
	m_fOCRing = false; 
 
	return(dlgret != IDCANCEL); 
} 
 
void CSubresyncDlg::FreeCharImgList() 
{ 
	POSITION p = m_cilist.GetHeadPosition(); 
	while(p) delete m_cilist.GetNext(p); 
	m_cilist.RemoveAll(); 
} 
 
void CSubresyncDlg::FreeCharSegmentList() 
{ 
	POSITION p = m_cslist.GetHeadPosition(); 
	while(p) delete m_cslist.GetNext(p); 
	m_cslist.RemoveAll(); 
} 
 
void CSubresyncDlg::ClearSelectedLines() 
{ 
	POSITION lpos = m_list.GetFirstSelectedItemPosition(); 
	while(lpos) m_list.SetItemState(m_list.GetNextSelectedItem(lpos), 0, LVIS_SELECTED); 
	SendMessage(WM_PAINT, 0, 0); 
} 
 
void CSubresyncDlg::SetSelectedLine(int i) 
{ 
	ClearSelectedLines(); 
	m_list.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED); 
	m_list.EnsureVisible(i, FALSE); 
} 
 
bool CSubresyncDlg::FindNextFilledRow(int& row) 
{ 
	uint* p = &m_p[row*m_w]; 
	 
	for(; row < m_h; row++) 
	{ 
		for(int col = 0; col < m_w; col++, p++) 
		{ 
			if(*p & 0xff000000) return(true); 
		} 
	} 
	 
	return(false); 
} 
 
bool CSubresyncDlg::FindNextEmptyRow(int& row, int minrowheight) 
{ 
	int srow = row-1; 
 
	for(; row < m_h; row++) 
	{ 
		uint* p = &m_p[row*m_w]; 
		 
		for(int col = 0; col < m_w; col++, p++) 
		{ 
			if(*p & 0xff000000)  
				break; 
		} 
		 
		if(col == m_w) 
		{ 
			if((row - srow) < minrowheight) 
			{ 
				int tmp = row; 
				if(FindNextFilledRow(tmp) && (tmp - srow) < minrowheight*3/2) 
				{ 
					row = tmp-1; 
					continue; 
				} 
			} 
 
			return(true); 
		} 
	} 
	 
	return(false); 
} 
 
bool CSubresyncDlg::FindNextFilledCol(int* left) 
{ 
	int row = m_srow; 
	 
	while(row < m_erow && left[row] < m_w) 
	{ 
		for(int y = row; y <= m_erow; y++) 
		{ 
			if(m_p[y*m_w + left[y]] & 0xff000000) return(true); 
			 
			left[y]++; 
		} 
		 
		while(left[row] >= m_w && row <= m_erow) row++; 
	} 
	 
	return(false); 
} 
 
bool CSubresyncDlg::FindNextEmptyCol(int* right) 
{ 
	int row = m_srow; 
	 
	while(right[row] >= m_w && row <= m_erow) row++; 
	 
	while(row <= m_erow && right[row] < m_w) 
	{ 
		for(int y = row; y <= m_erow; y++) 
		{ 
			if(m_p[y*m_w + right[y]] & 0xff000000)  
			{ 
				while((++right[y]) < m_w && (m_p[y*m_w + right[y]] & 0xff000000)); 
				break; 
			} 
		} 
		 
		if(y > m_erow) return(true); 
		 
		for(int y2 = y-1; y2 >= m_srow; y2--) 
		{ 
			if(right[y2] < (right[y2+1])) right[y2] = right[y2+1]; 
		} 
		 
		for(y2 = y+1; y2 <= m_erow; y2++) 
		{ 
			if(right[y2] < (right[y2-1]-1)) right[y2] = right[y2-1]-1; 
		} 
		 
		while(right[row] >= m_w && row <= m_erow) row++; 
	} 
	 
	return(false); 
} 
 
CRect CSubresyncDlg::ClipChar(int* left, int* right) 
{ 
	CRect ret; 
	 
	ret.left = 100000; 
	ret.right = -1; 
	 
	int row; 
	 
	for(row = m_srow; row <= m_erow; row++) 
	{ 
		for(int col = left[row]; col <= right[row]; col++) 
		{ 
			if(m_p[row*m_w + col] & 0xff000000) 
			{ 
				if(ret.left > col) ret.left = col; 
			} 
		} 
	} 
	 
	for(row = m_srow; row <= m_erow; row++) 
	{ 
		for(int col = right[row]; col >= left[row]; col--) 
		{ 
			if(m_p[row*m_w + col] & 0xff000000) 
			{ 
				if(ret.right < col) ret.right = col; 
			} 
		} 
	} 
	 
	for(ret.top = m_srow; ret.top <= m_erow; ret.top++) 
	{ 
		uint* p1 = &m_p[ret.top*m_w + left[ret.top]]; 
		uint* p2 = &m_p[ret.top*m_w + right[ret.top]]+1; 
		 
		while(p1 < p2 && !(*p1++ & 0xff000000)); 
		 
		if(p1 != p2) break; 
	} 
	 
	for(ret.bottom = m_erow; ret.bottom > ret.top; ret.bottom--) 
	{ 
		uint* p1 = &m_p[ret.bottom*m_w + left[ret.bottom]]; 
		uint* p2 = &m_p[ret.bottom*m_w + right[ret.bottom]]+1; 
		 
		while(p1 < p2 && !(*p1++ & 0xff000000)); 
		 
		if(p1 != p2) break; 
	} 
	 
	return(ret); 
} 
 
double CSubresyncDlg::GetGapBetweenTwoChars(CharSegment* cs1, CharSegment* cs2) 
{ 
	if(m_srow > m_erow) return(0); 
 
	CRect r1 = ClipChar(cs1->left, cs1->right); 
	CRect r2 = ClipChar(cs2->left, cs2->right); 
 
	bool fInLine = TopBottom(r1) == TopBottom(r2); 
 
	int horgap = r2.left - r1.right; 
	int leftgap = horgap; 
	int rightgap = horgap; 
 
	if(fInLine) 
	{ 
		r1.right = r2.right; 
		r2.left = r1.left; 
		CRect r = r1 & r2; 
		if(r.IsRectEmpty())  
		{ 
			r.top = r1.top < r2.top ? r1.bottom : r2.bottom; 
			r.bottom = r1.bottom > r2.bottom ? r1.top : r2.top; 
		} 
 
		int minr1x = 100000, maxr1x = -1; 
 
		for(int y = r.top; y <= r.bottom; y++) 
		{ 
			if(minr1x > cs1->right[y]) minr1x = cs1->right[y]; 
			if(maxr1x < cs1->right[y]) maxr1x = cs1->right[y]; 
		} 
 
		int minr2x = 100000, maxr2x = -1; 
 
		for(y = r.top; y <= r.bottom; y++) 
		{ 
			if(minr2x > cs2->left[y]) minr2x = cs2->left[y]; 
			if(maxr2x < cs2->left[y]) maxr2x = cs2->left[y]; 
		} 
 
		leftgap = minr2x - minr1x; 
		rightgap = maxr2x - maxr1x; 
	} 
 
	return(1.0 * (horgap*2 + leftgap + rightgap) / 4); 
} 
 
int CSubresyncDlg::TopBottom(CRect r) 
{ 
	int mid = (m_srow + m_erow) / 2; 
 
	return(r.bottom < mid ? -1 : r.top > mid ? 1 : 0); 
} 
 
bool CSubresyncDlg::LookupCharImg(CString& str, POSITION startpos, POSITION endpos, POSITION& newendpos) 
{ 
	if(!endpos) return(false); 
 
	POSITION orgendpos = endpos; 
	endpos = m_cslist.GetTailPosition(); 
	while(endpos) 
	{ 
		CharSegment* startcs = m_cslist.GetAt(startpos); 
		CharSegment* endcs = m_cslist.GetAt(endpos); 
 
		CRect r = ClipChar(startcs->left, endcs->right); 
/* 
		if(r.Width() > 40 && endpos != orgendpos)  
		{ 
			m_cslist.GetPrev(endpos); 
			continue; 
		} 
*/ 
		str.Empty(); 
 
		CharImg csimg(m_p, m_w, r, startcs->left, endcs->right, TopBottom(r)); 
 
		POSITION pos = m_cilist.GetHeadPosition(); 
		while(pos) 
		{ 
			POSITION cur = pos; 
 
			CharImg* img = m_cilist.GetNext(pos); 
 
			if(img->Match(&csimg)) 
			{ 
				str = img->m_str; 
				newendpos = endpos; 
 
				m_cilist.RemoveAt(cur); 
				m_cilist.AddHead(img); 
				return(true); 
			} 
		} 
 
		if(endpos == orgendpos) break; 
		 
		m_cslist.GetPrev(endpos); 
	} 
 
	return(false); 
} 
 
static bool _istssep(TCHAR c) 
{ 
	return(c == ',' || c == '.' || c == '?' || c == '!'); 
} 
 
//#include  
 
void CSubresyncDlg::CorrectSpellingErrors(CString& str) 
{ 
/*	CString str2; 
 
	CAtlRegExp<> reUrl; 
	CAtlREMatchContext<> mcUrl; 
	const CAtlREMatchContext<>::RECHAR* szStart; 
	const CAtlREMatchContext<>::RECHAR* szEnd; 
 
	if(REPARSE_ERROR_OK != reUrl.Parse(_T("{ +}{[,>\\.\\?\\!\\)\\]\\}]}"))) return; 
 
	for(TCHAR* ptr = str.GetBuffer();  
		reUrl.Match(ptr, &mcUrl) && mcUrl.m_uNumGroups == 2;  
		ptr = (TCHAR*)szEnd) 
	{ 
		mcUrl.GetMatch(0, &szStart, &szEnd); 
		str2.Append(ptr, (TCHAR*)szStart - ptr); 
		mcUrl.GetMatch(1, &szStart, &szEnd); 
		str2.Append((TCHAR*)szStart, (TCHAR*)szEnd - (TCHAR*)szStart); 
		 
	} 
 
	str = str2 + ptr; 
 
	if(REPARSE_ERROR_OK != reUrl.Parse(_T("{[,>\\.\\?\\!\\)\\]\\}]}[^ ,>\\.\\?\\!\\)\\]\\}]"))) return; 
 
	for(TCHAR* ptr = str.GetBuffer(); 
		reUrl.Match(ptr, &mcUrl) && mcUrl.m_uNumGroups == 1;  
		ptr = (TCHAR*)szEnd) 
	{ 
		mcUrl.GetMatch(0, &szStart, &szEnd); 
		str2.Append(ptr, (TCHAR*)szEnd - ptr); 
		str2 += ' '; 
	} 
*/ 
	int i = 0; 
	while((i = str.Find(_T("I"), i+1)) > 0) 
	{ 
		if(i > 0 && !_istspace(str[i-1]) && _istlower(str[i-1])) str.SetAt(i, 'l'); 
	} 
	i = -1; 
	while((i = str.Find(_T("l"), i+1)) >= 0) 
	{ 
		for(int j = i-1; j >= 0 && _istspace(str[j]); j--); 
		if(j < 0 || str[j] == '.' || str[j] == '?' || str[j] == '!') str.SetAt(i, 'I'); 
	} 
 
	str.Replace(_T(" ,"), _T(",")); 
	str.Replace(_T(" ."), _T(".")); 
	str.Replace(_T(" ?"), _T("?")); 
	str.Replace(_T(" !"), _T("!")); 
	str.Replace(_T("''"), _T("\"")); 
 
	if(str.GetLength() >= 2 && str.Left(2) == _T("l ")) str.SetAt(0, 'I'); 
	if(str.GetLength() >= 3) 
	{ 
		CString left3 = str.Left(3); 
		if(left3 == _T("ln ")) str.SetAt(0, 'I'); 
		if(left3 == _T("ls ")) str.SetAt(0, 'I'); 
		if(left3 == _T("lt ")) str.SetAt(0, 'I'); 
		if(left3 == _T("lf ")) str.SetAt(0, 'I'); 
	} 
 
	str.Replace(_T("\nl "), _T("\nI ")); 
	str.Replace(_T("\nln "), _T("\nIn ")); 
	str.Replace(_T("\nls "), _T("\nIs ")); 
	str.Replace(_T("\nlt "), _T("\nIt ")); 
	str.Replace(_T("\nlf "), _T("\nIf ")); 
	 
	str.Replace(_T(" l "), _T(" I ")); 
	str.Replace(_T(" ln "), _T(" In ")); 
	str.Replace(_T(" ls "), _T(" Is ")); 
	str.Replace(_T(" lt "), _T(" It ")); 
	str.Replace(_T(" lf "), _T(" If ")); 
	 
	str.Replace(_T("-l "), _T("-I ")); 
	str.Replace(_T("l'm"), _T("I'm")); 
	str.Replace(_T("l'd "), _T("I'd ")); 
	str.Replace(_T("l'll"), _T("I'll")); 
	str.Replace(_T("l've"), _T("I've")); 
	 
	str.Replace(_T("lt's"), _T("It's")); 
 
	str.Replace(_T("Y ou"), _T("You")); 
} 
 
// CharImg 
 
CharImg::CharImg(uint* p, int pitch, CRect r, int* left, int* right, int topbottom, CString str)  
	: m_topbottom(topbottom) 
	, m_str(str) 
{ 
	m_w = r.Width()+1;  
	m_h = r.Height()+1; 
 
	m_p = new bool[m_w*m_h]; 
	if(!m_p) return; 
 
	memset(m_p, 0, m_w*m_h*sizeof(bool)); 
	 
	for(int row = r.top; row <= r.bottom; row++) 
	{ 
		int rowmin = max(r.left, left[row]); 
		int rowmax = min(r.right, right[row]); 
		 
		if(rowmin > rowmax) continue; 
 
		bool* bp = &m_p[(row - r.top)*m_w + (rowmin - r.left)]; 
		uint* ip = &p[row*pitch + rowmin]; 
 
		for(int rowsize = rowmax-rowmin+1; rowsize > 0; rowsize--, bp++, ip++) 
		{ 
			*bp = !!(*ip>>24); 
		} 
	} 
} 
 
CharImg::CharImg(FILE* f) : m_p(NULL) 
{ 
	Read(f); 
} 
 
CharImg::~CharImg() 
{ 
	if(m_p) delete [] m_p; 
} 
 
static void resample(int* d, int dlen, int* s, int slen, int scale /* 24:8 fixed */, bool fTrace = false) 
{ 
	int i; 
 
	if(fTrace) 
	{ 
		TRACE(_T("before resampling: ")); 
		for(i = 0; i < slen; i++) TRACE(_T("%d, "), s[i]); 
		TRACE(_T("\n")); 
	} 
 
	int stepsize = 256 * slen / dlen; 
 
	if(!slen) 
	{ 
		memset(d, 0, dlen*sizeof(int)); 
		if(fTrace) TRACE(_T("after resampling: 0\n")); 
		return; 
	} 
	else if(dlen == slen) // nothing to do 
	{ 
		for(int i = 0; i < dlen; i++) 
			d[i] = s[i]<<8; 
	} 
	else if(slen < dlen) // if we are lucky lets try the faster version first 
	{ 
		for(int di = 0, si = 0; di < dlen; di++) 
		{ 
			int si2 = si + stepsize; 
 
			int starthigh = si>>8; 
			int startlow = (si&0xff); 
			int endhigh = si2>>8; 
			int endlow = (si2&0xff); 
			 
			if(starthigh == endhigh || endlow == 0) 
			{ 
				d[di] = s[starthigh] * stepsize; 
			} 
			else 
			{ 
				d[di] = s[starthigh] * (256-startlow) + s[endhigh] * endlow; 
			} 
 
			si = si2; 
		} 
	} 
	else 
	{ 
		for(int di = 0, si = 0; di < dlen; di++) 
		{ 
			int si2 = si + stepsize; 
 
			int starthigh = si>>8; 
			int startlow = (si&0xff); 
			int endhigh = si2>>8; 
			int endlow = (si2&0xff); 
 
			d[di] += s[starthigh] * (256-startlow); 
 
			for(starthigh++; starthigh < endhigh; starthigh++) 
				d[di] += (s[starthigh]<<8); 
 
			if(endlow > 0) 
				d[di] += s[endhigh] * endlow; 
 
			si = si2; 
		} 
	} 
 
	for(i = 0; i < dlen; i++) 
		d[i] = d[i] * scale / (stepsize<<8); 
 
	if(fTrace) 
	{ 
		TRACE(_T("after resampling: ")); 
		for(i = 0; i < dlen; i++) TRACE(_T("%d, "), d[i]); 
		TRACE(_T("\n")); 
	} 
 
	// phew, this sucked... 
} 
 
static void median3(int* s, int slen, bool fTrace = false) 
{ 
	if(slen <= 0) return; 
 
	if(fTrace) 
	{ 
		TRACE(_T("before median3: ")); 
		for(int i = 0; i < slen; i++) TRACE(_T("%d, "), s[i]); 
		TRACE(_T("\n")); 
	} 
 
	if(slen > 1) 
	{ 
		for(int prev = 0, i = 0; i < slen; i++) 
		{ 
			int cur = s[i]; 
			int next = i < slen-1 ? s[i+1] : 0; 
 
			if(cur <= next) 
			{ 
				if(prev > cur) 
				{ 
					s[i] = min(prev, next); 
				} 
			} 
			else 
			{ 
				if(prev < cur) 
				{ 
					s[i] = max(prev, next); 
				} 
			} 
 
			prev = cur; 
		} 
	} 
 
	if(fTrace) 
	{ 
		TRACE(_T("after median3: ")); 
		for(int i = 0; i < slen; i++) TRACE(_T("%d, "), s[i]); 
		TRACE(_T("\n")); 
	} 
} 
 
static void finddeltas(int* d, int dlen, int* s, int slen, int scale /* 24:8 fixed */, bool fTrace = false) // s will be partially destroyed 
{ 
	if(slen <= 0) return; 
 
	if(fTrace) 
	{ 
		TRACE(_T("before finddeltas: ")); 
		for(int i = 0; i < slen; i++) TRACE(_T("%d, "), s[i]); 
		TRACE(_T("\n")); 
	} 
 
	int len = 0; 
 
	if(slen > 1)  
	{ 
		for(int sum = 0, i = 0; i <= slen; i++) 
		{ 
			int diff = i == 0 ? s[i] : i == slen ? (0-s[i-1]) : (s[i]-s[i-1]); 
 
			if(diff*sum < 0)  
			{ 
				s[len++] = sum;  
				sum = 0; 
			} 
 
			sum += diff; 
		} 
	} 
	else 
	{ 
		len = 1; 
	} 
 
	resample(d, dlen, s, len, scale, fTrace); 
} 
 
bool CharImg::Match(CharImg* img) 
{ 
	return(m_p && img->m_p  
		&& m_topbottom == img->m_topbottom  
		&& m_w == img->m_w && m_h == img->m_h  
		&& !memcmp(m_p, img->m_p, m_w*m_h*sizeof(bool))); 
} 
 
bool CharImg::Write(FILE* f) 
{ 
	uint ID = 0x12345678; 
	fwrite(&ID, 4, 1, f); 
 
	int len = m_str.GetLength(); 
	fwrite(&len, 4, 1, f); 
 
#ifndef UNICODE 
	WCHAR* buff = new WCHAR[len+1]; 
	if(!buff) return(false); 
	mbstowcs(buff, m_str, len+1); 
	fwrite(buff, 2, len, f); 
	delete [] buff; 
#else 
	fwrite(m_str, 2, len, f); 
#endif 
 
	fwrite(&m_w, 4, 1, f); 
	fwrite(&m_h, 4, 1, f); 
 
	bool* p = m_p; 
 
	for(int i = 0, j = m_w*m_h, c = 0; i < j; i++, p++) 
	{ 
		if(!(i&7) && i > 0)  
		{ 
			fputc(c, f); 
			c = 0; 
		} 
 
		if(*p)  
			c |= (1<<(i&7)); 
	} 
 
	if(i > 0)  
		fputc(c, f); 
 
	fputc(m_topbottom, f); 
 
	return(true); 
} 
 
bool CharImg::Read(FILE* f) 
{ 
	if(m_p) {delete [] m_p; m_p = NULL;} 
	 
	m_w = m_h = 0; 
 
	uint ID; 
	if(fread(&ID, 4, 1, f) != 1 || ID != 0x12345678) return(false); 
 
	int len; 
	if(fread(&len, 4, 1, f) != 1) return(false); 
 
	WCHAR* buff = new WCHAR[len+1]; 
	if(!buff) return(false); 
	if(fread(buff, 2, len, f) != len) {delete [] buff; return(false);} 
	buff[len] = 0; 
	m_str = buff; // warning: MCBS build will use ANSI here 
	delete [] buff; 
 
	if(fread(&m_w, 4, 1, f) != 1) return(false); 
	if(fread(&m_h, 4, 1, f) != 1) return(false); 
 
	m_p = new bool[m_w*m_h]; 
	if(!m_p) return(false); 
 
	for(int i = 0, j = m_w*m_h, c = 0; i < j; i++) 
	{ 
		if(!(i&7))  
		{ 
			if((c = fgetc(f)) == EOF)  
			{ 
				delete [] m_p; m_p = NULL; 
				m_w = m_h = 0; 
				return(false); 
			} 
		} 
 
		m_p[i] = !!(c&(1<<(i&7))); 
	} 
 
	m_topbottom = (char)fgetc(f); 
 
	return(true); 
} 
 
// CharSegment 
 
CharSegment::CharSegment(int* left, int* right, int h, int srow, int erow) 
{ 
	this->left = this->right = NULL; 
	 
	this->left = new int[h]; 
	if(!this->left) return; 
	 
	this->right = new int[h]; 
	if(!this->right) {delete [] left; return;} 
	 
	memcpy(this->left, left, h*sizeof(int)); 
	memcpy(this->right, right, h*sizeof(int)); 
	 
	this->h = h; 
	this->srow = srow; 
	this->erow = erow; 
} 
 
CharSegment::~CharSegment() 
{ 
	if(left) delete [] left;  
	if(right) delete [] right; 
}