在工作中遇到這樣一個問題,在即時監視的視頻顯示中需要在視頻中顯示目前時間,通道名等額外資訊,無論資訊內容在視頻畫面上採用何種顏色顯示,當資訊顯示地區背景的視頻畫面的顏色正好與顯示資訊內容的顏色一致或差不多相似時,這個時候,很難看清顯示的內容,儘管這種情況出現的機率並不大,但可能性仍然存在,解決這個問題最好的方法是在資訊文字的外圍加上一層和文字顏色不一樣的外邊。下面我們以文字為白色,外圍加黑邊為例來分析這個問題。
以下分析基於在windows作業系統的PC機上實現。
如何在白色文字的外圍加裹一層黑邊,目前我知道的有兩種方式。
1.通過獲得windows顯示裝置控制代碼,利用windows的textout()函數可以直接裹上黑邊。具體代碼如下:
bool drawText(CDC *pDC,
LPCTSTR lpszText,
const CRect& rctDraw,
UINT nFormat,
COLORREF clrText,
COLORREF clrBack)
{
ASSERT(pDC != NULL);
if (pDC->GetSafeHdc() == NULL) return false;
COLORREF oldColor = pDC->GetTextColor();
int oldBkMod = pDC->SetBkMode(TRANSPARENT);
CRect rect = rctDraw;
pDC->SetTextColor(clrBack);
rect.OffsetRect(-1, -1); pDC->DrawText(lpszText, rect, nFormat);
rect.OffsetRect( 1, 0); pDC->DrawText(lpszText, rect, nFormat);
rect.OffsetRect( 1, 0); pDC->DrawText(lpszText, rect, nFormat);
rect.OffsetRect( 0, 1); pDC->DrawText(lpszText, rect, nFormat);
rect.OffsetRect( 0, 1); pDC->DrawText(lpszText, rect, nFormat);
rect.OffsetRect(-1, 0); pDC->DrawText(lpszText, rect, nFormat);
rect.OffsetRect(-1, 0); pDC->DrawText(lpszText, rect, nFormat);
rect.OffsetRect( 0, -1); pDC->DrawText(lpszText, rect, nFormat);
pDC->SetTextColor(clrText);
rect.OffsetRect( 1, 0); pDC->DrawText(lpszText, rect, nFormat);
pDC->SetBkMode(oldBkMod);
pDC->SetTextColor(oldColor);
return true;
}
2.通過已擷取的點陣字元庫,然後構建一個新的包含黑邊的字元庫結構,顯示時,對這個新的字元庫結構顯示。
以16*16的點陣字元庫為例,一個漢字需要16*16位即32個位元組來表示,如
在32個位元組裡存取的都是字模顯示的位資訊,2個位元組表示一行,按位從左至右顯示,0表示無色,1表示文字色,一共16行。
為了在字的外圍加一層黑邊,需要定義一個新的資料結構,用兩個位來儲存點陣資訊,00表示無色,11表示文字色,01表示邊框色,這樣考慮到為了在字的邊緣處也可能有黑邊,新的字元矩陣應為18*18,其中兩個位表示一個點陣資訊,這樣每一行需要18*2位,為了方便存取,每一行用5個位元組表示,從左至右按位填充資訊,多餘位補0。
具體演算法思路:
將含單色資訊的字元模資料展開到記憶體,然後第一次漸進式掃描點,凡是掃到某點位文字色,那麼在加黑邊後,新的資料結構中,其周圍8個點,只有兩種情況,一種是黑邊色點,一種是文字色點,暫時不去理會它是否文字色點,將其周圍8個點一律設為黑邊色點,其自身設為文字色點,這樣一次掃描完所有點後,顯然在新的資料結構中,有很多文字點被設為了黑邊色點,這樣需要再對單色資訊的字元模資料再掃描一遍,如掃到點為文字色,將新的資料結構中對應的點調為文字色,這樣就將上一次掃描中本是文字色點卻設為黑邊色點的所有點資訊全部調整過來了。
具體源碼如下:
void AddShadowToZM(BYTE *pZM,int nWidth,int nHeight,BYTE **ppShadowZM)
{
if(pZM == NULL)
{
return;
}
if(*ppShadowZM != NULL)
{
delete []*ppShadowZM;
}
//由於需要加上黑邊,考慮用兩個位元位來表示一位象素,00表示無象素顯示,01表示該象素為黑邊象素,11表示該象素為文字象素
//同時考慮到外圍需要加一層,在寬度上需要增加4個位元位,和位元影像對應從左至右,最前兩個位元位對應位元影像左上方原點
//在高度上增加兩行
*ppShadowZM = new BYTE[(nWidth*2+1)*(nHeight+2)];
memset(*ppShadowZM,0,(nWidth*2+1)*(nHeight+2));
int i,j,k;
//SHADOW
for(i = 0;i < nHeight;i++)
{
for(j = 0;j < nWidth;j++)
{
for(k = 0;k < 8;k++)
{
if((pZM[i*nWidth+j] >> (7-k))&0x01)
{
//TRACE("0");
//如果某一位為1,將其四周象素全部置為01,表示為加黑邊
for(int m = 0;m < 3;m++)
{
for(int n = 0;n < 3;n++)
{
(*ppShadowZM)[(i+m)*(nWidth*2+1)+(j*8+k+n)*2/8] |= 1<<((3-(k+n)%4))*2;
//TRACE("2");
}
}
}
else
{
//TRACE(" ");
}
}
}
//TRACE("/n");
}
for(i = 0;i < nHeight;i++)
{
for(j = 0;j < nWidth;j++)
{
for(k = 0;k < 8;k++)
{
if((pZM[i*nWidth+j] >> (7-k))&0x01)
{
(*ppShadowZM)[(i+1)*(nWidth*2+1)+(j*8+k+1)*2/8] |= 3<<((3-(k+1)%4))*2;
//TRACE("0");
}
else
{
//TRACE("*");
}
}
}
}
}
在擷取了新的點陣資料結構後,可以根據顯示的要求將其顯示在視頻畫面上。
上述的第二種方法,雖然比較麻煩,但在擷取了點陣字元模資料檔案後,這種方法更具普遍性。關於點陣字元模資料的擷取將另行討論。