位图上输出文字( 图形上增加文字 )

有两种方式:

1.通过CDC的成员函数直接输出文本

2.通过CDC的成员函数得到输出文字的点阵(轮廓信息),然后根据这些点阵位的0或1来判断是否要在原始位图数据上用此点阵数据来覆盖。

 

详解方式2:

参考网页:

http://linfengdu.blog.163.com/blog/static/11771073201031315358149/

 

 

MFC提供了一些API帮助用户方便的从各种系统字库中提取需要的点阵数据。大致上是使用这个函数:

DWORD GetGlyphOutline( UINT nChar, 
                       UINT nFormat, 
                       LPGLYPHMETRICS lpgm, 
                       DWORD cbBuffer, 
                       LPVOID lpBuffer, 
                       const MAT2 FAR* lpmat2 ) const; 
这个函数的各个参数的用法可以去查MSDN,利用这个函数的典型应用如下:

 

int FontSize = 48;

CString FontName = "宋体"; 

int ch = 'A';

//////////////////////////////////////////////////////
CDC dcScreen;
dcScreen.CreateDC("DISPLAY", NULL, NULL, NULL);
CFont newfont;
newfont.CreateFont(FontSize,
   0, 
   0,
   0, 
   0, 
   0,
   0,
   0,
   DEFAULT_CHARSET,
   OUT_CHARACTER_PRECIS,
   CLIP_CHARACTER_PRECIS, 
   DEFAULT_QUALITY,
   DEFAULT_PITCH|FF_DONTCARE,
   FontName);
CFont *poldfont=dcScreen.SelectObject(&newfont);
//////////////////////////////////////////////////////
GLYPHMETRICS pGL;
MAT2 mat2 = {
  {0, 1},
  {0, 0},
  {0, 0},
  {0, 1},
};

// 获取字符ch的点阵数据的大小
int len =dcScreen.GetGlyphOutline(ch, GGO_BITMAP, &pGL, 0, NULL, &mat2);
// 申请空间

unsigned char *pBuf = new unsigned char [len];

// 保存点阵数据

  dcScreen.GetGlyphOutline(ch, GGO_BITMAP, &pGL, len, pBuf, &mat2);
// 接下来就可以利用pBuf里的数据来显示了

// 最后记得释放掉pBuf,并还原Screen DC的字体

dcScreen.SelectObject(poldfont);
delete []pBuf;

 

 

使用上面的代码可以很方便的拿到字符ch的点阵数据。这里需要注意的是,获取到的点阵数据是横向取点,第一个点保存在第一个字节的最高位,另外,点阵数据的每一行都是要做4字节对齐的。

也就是说,48*48的点阵数据,它的每一行实际是8个字节,而不是48/8=6字节!

为了做4字节对齐,可以使用这样的方法来得到每一行的宽度:int FontWidth = ((FontSize + 31) >> 5) << 2;

上面的公式在网上随处可见,也是典型的有效的快速做对齐的方法。

由于在写这个软件的时候可能不仅仅这一个地方需要做对齐的操作,所以索性就写了一个对齐函数:

static const int sc_PowerMap[] = {
0, 0, 1, 0, 2, 0, 0, 0, 3,
};
int Power(int n)
{
if((n > 0) && (n < sizeof(sc_PowerMap) / sizeof(int)))
  return sc_PowerMap[n];
return -1;
}

#define ALIGN(num, align) (((((num) + (align) * 8 - 1) >> (3 + Power(align)))) << Power(align))

利用宏ALIGN就可以求得num的align字节对齐的结果,比如:ALIGN(32, 4)即可得到32做4字节对齐

当然,这里写的这个宏是有一定的局限性的,它只能做2的n次幂个字节的对齐,即,只能做1字节、2字节、4字节、8字节等对齐操作。不过,一般情况下这已经足够用了,因为很少有人变态到在二进制的计算机里使用3字节或5字节对齐这样的数据存储方式~~

得到字符的点阵数据之后,问题接踵而来。正如之前转的那个《根据所选择的 TrueType 字体生成点阵数据》里叙述的那样,GetGlyphOutline()函数得到的点阵数据仅仅是下图中的BlackBox的范围内的数据。

而通常情况下我们希望得到的点阵数据是完整的lfHeight*gmCellIncX这么大范围内的数据。所以,有必要将BlackBox这幅“点阵图像”按照Origin所指示的原点给它移动到我们希望的字符范围的中间。

在阅读《根据所选择的 TrueType 字体生成点阵数据》一文时,其实并没有搞清楚上面的图中的各个参数是如何得到的。通过GetGlyphOutline()函数中的LPGLYPHMETRICS参数,可以得到BlackBoxX和BlackBoxY,以及gmCellIncX和gmCellIncY,以及Origin坐标等信息。但是在调试的过程中发现,gmCellIncY为0 ?由于机器上的MSDN出了问题,没办法查它的意义,所以只好硬着头皮尝试。另外还发现,Origin坐标中的y坐标看起来并不是以字符外框的左上角为原点计算的。后来以为是以左下角为原点计算,所以只需要:

off_x = Origin.x;

off_y = lfHeight - Origin.y;

就可以得到字符在框框中的起始坐标了,但是实际操作的时候发现,其实并不是这样的……因为在对某个字符的数据跟踪的时候发现,BlackBoxY的值是16(16号字体下),Orign.y是14,这简直是不可能的!因为off_y = 16-14=2,而如果起始y坐标是2的话,那么BlackBoxY已经是16个像素高了,再向下移动2个像素就会超出字符的外框啊~

结果这个问题导致了调试过程中经常由于内存操作越界而导致堆block损坏!

后来再回头去看《根据所选择的 TrueType 字体生成点阵数据》一文发现,原来Origin坐标的计算方法不是这样的,需要配合另外一个函数GetTextMetrics()来实现。最后,完整的获取点阵数据的函数如下:

 

int CHZKCreatorDlg::GetFontData(int ch, CString FontName, int FontSize, void *pBuf/*=NULL*/, int x_off/*=0*/, int y_off/*=0*/)
{
// 小于4号的字体不再输出
if(FontSize < 4)
{
  if(pBuf == NULL)
   return FontSize;
  else
  {
   memset(pBuf, 0, FontSize);
   return FontSize;
  }
}
size_t RetSize = 0;
//////////////////////////////////////////////////////
CDC dcScreen;
dcScreen.CreateDC("DISPLAY", NULL, NULL, NULL);
CFont newfont;
newfont.CreateFont(FontSize,
  0, 
  0,
  0, 
  0, 
  0,
  0,
  0,
  DEFAULT_CHARSET,
  OUT_CHARACTER_PRECIS,
  CLIP_CHARACTER_PRECIS, 
  DEFAULT_QUALITY,
  DEFAULT_PITCH|FF_DONTCARE,
  FontName);
CFont *poldfont=dcScreen.SelectObject(&newfont);
//////////////////////////////////////////////////////
TEXTMETRIC tm;
GLYPHMETRICS pGL;
MAT2 mat2 = {
  {0, 1},
  {0, 0},
  {0, 0},
  {0, 1},
};
dcScreen.GetTextMetrics(&tm);
int len =dcScreen.GetGlyphOutline(ch, GGO_BITMAP, &pGL, 0, NULL, &mat2);
//////////////////////////////////////////////////////
//int Truelen = (ch & 0xff00) != 0 ? ALIGN(FontSize, 4) : ALIGN(FontSize / 2, 4);
int Truelen = (ch & 0xff00) != 0 ? ALIGN(FontSize, 1) : ALIGN(FontSize / 2, 1);
int Fontlen = ALIGN(pGL.gmBlackBoxX, 4);
int TransLen = Truelen > Fontlen ? Fontlen : Truelen;
int FontOffY = tm.tmAscent - pGL.gmptGlyphOrigin.y;
int FontOffX = pGL.gmptGlyphOrigin.x < 0 ? 0 : pGL.gmptGlyphOrigin.x;
RetSize = Truelen * FontSize;
if(pBuf != NULL)
{
  memset(pBuf, 0, RetSize);
  if(len > 0)
  {
   unsigned char *pSrc = new unsigned char [len];
   unsigned char *pDest = (unsigned char *)pBuf;
   dcScreen.GetGlyphOutline(ch, GGO_BITMAP, &pGL, len, pSrc, &mat2);
   for(int i = 0; i < len / Fontlen; i++)
   {
    memcpy(pDest + i * Truelen, pSrc + i * Fontlen, TransLen);
   }
   delete []pSrc;
   ConvertMetrux(pDest, Truelen, FontSize, x_off + FontOffX, y_off + FontOffY);
  }
}
dcScreen.SelectObject(poldfont);
return RetSize;
}

 

中间的循环memcpy的过程是将GetGlyphOutline()得到的数据copy至输出Buffer中,在这个过程中需要注意,由于希望输出的点阵数据的每行是1字节对齐而不是4字节对齐,所以copy长度需要计算一下。

最后,使用ConvertMetrux()函数来完成对点阵数据的偏移操作,以便将点阵数据放到外框的中间位置上去。其中,可以注意到,FontOffX和FontOffY就是根据Origin计算得到的需要做的偏移量。另外,x_off和y_off是希望留出一个比较灵活的结果,让用户可以对字体在点阵中的位置进行微调,最后,将Origin和用户微调的程度结合起来送给ConvertMetrux函数来完成数据在矩阵内的搬移。有关数据的搬移,今天就先不介绍啦,哈,卖个关子先~(嘿嘿,其实是怕出丑啦,代码写的比较烂

利用上面的这个函数就可以比较好的获取字符ch的点阵数据了。剩下的工作就是做一个循环,把所有的ch都调用一下这个函数,然后将得到的数据写入到文件里就OK啦~^o^

先把工具放出来让大家看看吧:-)

工具下载:http://leajian.9k9k.cn/upload/HZKCreator.rar

目前支持简体中文GB2312和英文ASCII

posted @ 2012-01-12 15:39  jqb  阅读(1182)  评论(0)    收藏  举报