3.7 图像字体

字体在图形、图像处理中具有举足轻重的作用,特殊的字体能够增加图像的显示效果。本节将通过几个实例介绍如何绘制特殊效果的字体。

实例116 旋转的文字

本实例是一个人性化的实例

实例位置:光盘\mingrisoft\03\116

实例说明

在一些多媒体应用软件中,一些文字信息并不是按水平方向或垂直方向显示,而是按一定的角度倾斜显示,效果很好。本实例实现了文字的旋转,效果如图3.39所示。

图3.39 旋转的文字

技术要点

实现字体的旋转非常简单,首先创建一个字体,在创建字体时指定倾斜角度,然后利用设备上下文选中字体,最后输出文字,这样文字就会在某一位置上按照字体指定的角度倾斜。创建一个字体可以调用CFont类的CreateFont方法,语法如下:

        BOOLCreateFont(int nHeight,int nWidth,int nEscapement,int nOrientation,int nWeight,BYTE bItalic,BYTE bUnderline,BYTE cStrikeOut,BYTE nCharSet,BYTE nOutPrecision,BYTE nClipPrecision,BYTE nQuality, BYTE nPitchAndFamily,LPCTSTR lpszFacename);

参数说明:

● nHeight、nWidth:用于指定字体的高度和宽度。为正数时,字体映射机制会根据指定的高度或宽度从字体列表中选择一种接近的字体,以该字体的单元高度或宽度为参考。为负数时,字体映射机制也会选择一种字体,不过这时以字体字符高度或宽度为参考。单元是在实际输出字符的上下有一些空隙,而字符是忽略掉这些空隙之后的单元高度。

● nEscapement:以x轴为参考确定文本的倾斜角度。

● nOrientation:确定字符基线与x轴的倾斜角度。

● nWeight:确定字体的重量,即字体的粗细程度。

● bItalic:确定是否是斜体。

● bUnderline:确定是否有下划线。

● cStrikeOut:确定是否有删除线。

● nCharSet:用于指定字符集。

● nOutPrecision:确定字体映射机制如何根据提供的参数选择合适的字体。

● nClipPrecision:用于确定字体的裁减精度。当文本的一部分延伸到指定区域外时,该参数用于确定文本被裁减的方式。

● nQuality:用于确定字体显示的品质。

● nPitchAndFamily:用于确定字符间距和字体属性。

● lpszFacename:确定字体名称。

例如实现一个简单的旋转文字,代码如下:

        CFont m_font;
        m_font.CreateFont(-14,-10,i*10,0,600,0,0,0,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,
        CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FF_ROMAN,"宋体");

实现过程

(1)新建一个基于对话框的应用程序。

(2)主要程序代码如下:

          CDC*pDC=GetDC();                                                            //获得设备上下文
          CFont m_font;
          pDC->SetBkMode(TRANSPARENT);                                                //设置背景透明
          CRect m_rect;
          GetClientRect(m_rect);                                                      //获得客户区域
          pDC->FillRect(m_rect,NULL);                                                 //填充区域
          pDC->SetViewportOrg(m_rect.Width()/2,m_rect.Height()/2);                    //设置原点
          for (int i = 1;i< 360;i+=18)
          {
              m_font.CreateFont(-14,-10,i*10,0,600,0,0,0,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,
                  CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FF_ROMAN,"宋体");               //创建字体
              pDC->SelectObject(&m_font);                                             //选入字体
              pDC->SetTextColor(RGB(255-i,i*50,i));                                   //设置文本颜色
              pDC->TextOut(0,0,"明日科技有限公司");                                   //输出文本
              m_font.DeleteObject();
          }

举一反三

根据本实例,读者可以:

修改任意控件的字体属性。

实例117 当前系统字体列表

这是一个可以提高基础性能的实例

实例位置:光盘\mingrisoft\03\117

实例说明

用过Visual Basic、Delphi的读者知道,在设置控件字体时,只要在控件的属性对话框中单击字体属性时,就会出现一个字体列表,供用户选择,使用起来比较方便。本实例实现了利用Visual C++获取字体列表的功能,效果如图3.40所示。

图3.40 当前系统字体列表

技术要点

Windows操作系统提供了一个EnumFontFamiliesEx函数,该函数用于获取系统字体列表,其语法如下:

          int EnumFontFamiliesEx(HDC hdc, LPLOGFONT lpLogfont, FONTENUMPROC lpEnumFontFamExProc,
          LPARAM lParam, DWORD dwFlags );

参数说明:

● hdc:是一个设备上下文句柄。

● lpLogfont:是LOGFONT结构指针,用于描述列举的字体信息。如果设置LOGFONT结构的lfCharset成员为DEFAULT_CHARSET,函数将列举所有字符集的字体,否则只列举指定的字符集字体。

● lpEnumFontFamExProc:是列举字体的一个函数指针,语法如下:

          int CALLBACK EnumFontFamExProc(ENUMLOGFONTEX *lpelfe, NEWTEXTMETRICEX *lpntme,
          int FontType,LPARAM lParam );

参数说明:

● lpelfe:是一个ENUMLOGFONTEX结构指针,该结构包含了列举的字体信息。

● lpntme:是一个NEWTEXTMETRICEX结构指针,该结构包含了列举的字体物理信息。

● FontType:描述了列举到的字体类型。

● lParam:是由EnumFontFamiliesEx函数传递的附加信息。

实际上EnumFontFamiliesEx函数不断地调用lpEnumFontFamExProc函数指针所指的函数,向其参数传递字体信息。用户只要定义一个与lpEnumFontFamExProc具有相同函数原型的函数,在该函数中读取lpelfe参数的lfFaceName成员,将其存储在一个列表中,就可以获取字体名称了。lParam是应用程序定义的一个附加信息。dwFlags是保留字,必须为零。

通过了解EnumFontFamiliesEx函数,读者会得到很多启发,函数指针可以作为函数参数,这样的函数具有很大的弹性。因为函数指针所指的函数由用户自己定义,同一个函数由于参数函数指针所指的函数不同,实现的功能也不同。类似的函数在Windows系统中有很多,例如CreateThread函数。在创建一个线程时,线程需要执行一个函数(线程函数),在设计CreateThread函数时,参数中就提供了一个函数指针,由用户定义线程函数的行为,将其传递给函数指针参数,在CreateThread函数内部就可以访问线程函数了。

实现过程

(1)新建一个基于对话框的应用程序。

(2)在对话框类中放置静态文本和组合框控件。

(3)定义一个回调函数,用于获取列举的字体名称,代码如下:

        CStringList fontlist;
        int CALLBACK EnumFontList (const ENUMLOGFONTEX *lpelfe, const NEWTEXTMETRICEX *lpntme,
        unsigned long FontType, LPARAM lParam)
        {
            POSITION pos=fontlist.Find(lpelfe->elfLogFont.lfFaceName);     //获得字体名称
            if (pos == NULL)
            fontlist.AddTail(lpelfe->elfLogFont.lfFaceName);
            return 1 ;
        }

(4)调用EnumFontFamiliesEx函数列举字体,代码如下:

        CDC* dc = GetDC();
        CString str;
        fontlist.RemoveAll();
        m_FontList.ResetContent();
        LOGFONT m_logfont;
        memset(&m_logfont,0,sizeof(m_logfont));
        m_logfont.lfCharSet = DEFAULT_CHARSET;
        m_logfont.lfFaceName[0] =NULL;
        EnumFontFamiliesEx(dc->m_hDC,&m_logfont,(FONTENUMPROC)EnumFontList,100,0);   //枚举系统字体
        POSITION pos;
        for ( pos =fontlist.GetHeadPosition() ;pos != NULL;)
        {
            str = fontlist.GetNext(pos);
            m_FontList.AddString(str);  //将字体名称插入到组合框中
        }

举一反三

根据本实例,读者可以:

列举系统中的各种资源。

实例118 空心文字

本实例是一个人性化的实例

实例位置:光盘\mingrisoft\03\118

实例说明

Windows提供了一个空心的字体——华文彩云,该字体在设备上输出时是空心的,效果比较好。本实例实现了宋体空心文本,效果如图3.41所示。

图3.41 空心文字

技术要点

实现字体的空心显示,可以利用设备上下文CDC类的通道方法。CDC类提供了多个通道方法,其主要方法如下:

(1)BeginPath。该方法用于开始一个通道,语法如下:

BOOL BeginPath( );

(2)EndPath。该方法用于结束一个通道,语法如下:

BOOL EndPath( );

(3)StrokePath。该方法用当前的画笔绘制一个通道,语法如下:

BOOL StrokePath( );

实现过程

(1)新建一个基于对话框的应用程序。

(2)在对话框类中添加DrawForm方法绘制网格,代码如下:

          void CPathFontDlg::DrawForm()
          {
              CDC*dc=GetDC();                          //获得设备上下文
              CRect m_rect;
              GetClientRect(m_rect);                   //获得客户区域
              CBrush brush;
              brush.CreateStockObject(WHITE_BRUSH);    //创建画刷
              dc->FillRect(m_rect,&brush);             //填充区域
              CPen m_pen(PS_SOLID,1,RGB(63,126,126)); //创建画笔
              dc->SelectObject(&m_pen);
              for(int x=1;x<m_rect.right;x+=10)         //绘制竖线
              {
              dc->MoveTo(x,0);
              dc->LineTo(x,m_rect.bottom);
              }
              for(int y=1;y<m_rect.bottom;y+=10)        //绘制横线
              {
              dc->MoveTo(0,y);
              dc->LineTo(m_rect.right,y);
              }
          }

(3)绘制空心文字,代码如下:

          CDC* pDC = GetDC();
          CRect m_rect;
          GetClientRect(m_rect);
          m_font.CreateFont(-32,-28,0,0,600,0,0,0,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,
              CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FF_ROMAN,"宋体");   //创建字体
          pDC->BeginPath();                       //打开通道
          pDC->SelectObject(&m_font);
          pDC->SetBkMode(TRANSPARENT);            //设置背景透明
          pDC->TextOut(100,70," 明日科技");       //输出字体
          CPen pen(PS_SOLID,1,RGB(255,0,255));     //设置画笔
          pDC->SelectObject(&pen);
          pDC->EndPath();                         //关闭通道
          m_font.Detach();                        //释放字体
          pDC->StrokePath();

举一反三

根据本实例,读者可以:

设计字型窗体。

实例119 彩虹文字

这是一个可以提高分析能力的实例

实例位置:光盘\mingrisoft\03\119

实例说明

在窗体中显示彩虹文字,可以美化程序界面。本实例实现的是在窗体中显示彩虹文字,运行程序如图3.42所示。

图3.42 彩虹文字

技术要点

在窗体中显示彩虹文字,需要调用CDC类的BeginPath、EndPath和AbortPath等方法使用通道,并使用随机函数rand来设置颜色。

实现过程

(1)新建一个基于对话框的应用程序。

(2)主要程序代码如下:

        void CRainbowDlg::OnTimer(UINT nIDEvent)
        {
            CDC*pDC=GetDC();                                      //获得设备上下文
            Font.CreatePointFont(400,"宋体",pDC);                 //创建字体
            pDC->SelectObject(&Font);
            pDC->BeginPath();                                     //打开通道
            pDC->SetBkMode(TRANSPARENT);                          //设计背景透明
            pDC->TextOut(70,45,"彩虹文字");                       //输出文字
            pDC->EndPath();                                       //关闭通道
            pDC->SelectClipPath(RGN_COPY);
            pDC->AbortPath();
            Font.DeleteObject();
            CRect rect;
            GetClientRect(&rect);
            int R,G,B;
            for(int i=0;i<rect.Height();i=i+5)
            {
              //随机选择颜色
              R = rand()/2;
              G = rand()/2;
              B = rand()/2;
              CPen pen;
              pen.CreatePen(PS_SOLID,5,RGB(255*R,255*G,255*B));//创建画笔
              pDC->SelectObject(&pen);
              pDC->MoveTo(rect.Width(),i);
              pDC->LineTo(0,i);
              pen.DeleteObject();
            }
            CDialog::OnTimer(nIDEvent);
        }

举一反三

根据本实例,读者可以:

实现跟随鼠标移动的图片。

实例120 如何在图片上平滑移动文字

这是一个可以提高分析能力的实例

实例位置:光盘\mingrisoft\03\120

实例说明

在图片界面中,可以根据需要在图片上移动文字或一些卡通图片,使图片更具有个性化和动态性。本实例实现的是在图片上移动文字,如图3.43所示,并且文字可以移出图片的显示范围。

图3.43 如何在图片上平滑移动文字

技术要点

在图片上移动文字比较简单,可以利用CStatic控件标识图片上的文字信息。在鼠标拖动文字时,只需要调整CStatic控件的位置就可以了。Windows并没有提供鼠标拖动的消息,但是可以根据鼠标拖动的开始时间和生存期确定鼠标拖动消息。鼠标拖动消息的起点应该在鼠标按下并开始移动时,终点在用户释放鼠标按钮时。为了标识鼠标是否处于拖动状态,可以定义一个布尔型成员变量m_IsDowned,在用户按下鼠标按钮时,将其设置为TRUE,表示开始鼠标拖动,在用户释放鼠标按钮时,将其设置为FALSE,表示结束鼠标拖动。在鼠标移动过程中,判断m_IsDowned是否为TRUE,如果是则表明用户正在进行拖动操作,此时可以移动CStatic控件。这样就实现了CStatic控件的拖动。

实现过程

(1)新建一个基于对话框的应用程序。

(2)在对话框中添加静态文本控件和图片控件。

(3)从CStatic类派生一个子类CMyStatic,在该类中定义一个变量m_font,用于设置文本的字体。

(4)处理CMyStatic的WM_PAINT消息,绘制文本,代码如下:

          void CMyStatic::OnPaint()
          {
              CPaintDC dc(this);
              CDC* pDC = GetDC();
              pDC->SetBkMode(TRANSPARENT);            //设置背景透明
              pDC->SelectObject(&m_font);             //选入字体
              pDC->SetTextColor(RGB(255,0,0));        //设置文本颜色
              CString str;
              this->GetWindowText(str);               //获得显示文本
              pDC->TextOut(0,2,str);                  //绘制文本
          }

(5)处理对话框的WM_LBUTTONDOWN消息,记录鼠标按下时的坐标,代码如下:

          void CMoveTextDlg::OnLButtonDown(UINT nFlags, CPoint point)
          {
              CRect m_client;
              m_title.GetClientRect(m_client);            //获得客户区域
              m_title.MapWindowPoints(this,m_client);     //转为屏幕区域
              if(m_client.PtInRect(point))                //如果鼠标在文本区域内
                m_IsDowned = TRUE;
              m_start=point;                              //设置当前鼠标位置为起点
              //设置终点坐标
              CRect m_rect1;
              m_title.GetClientRect(m_rect1);
              m_title.MapWindowPoints(this,m_rect1);
              m_end.x = m_rect1.left;
              m_end.y = m_rect1.top;
              CDialog::OnLButtonDown(nFlags, point);
          }

(6)处理对话框的WM_MOUSEMOVE消息,判断是否拖动控件,如果拖动控件,根据当前鼠标点移动控件,代码如下:

          void CMoveTextDlg::OnMouseMove(UINT nFlags, CPoint point)
          {
              if (m_IsDowned)
              {
              m_IsMove = TRUE;
              int x,y;
              x = point.x-m_start.x;
              y = point.y-m_start.y;
              m_title.GetClientRect(m_rect);
              x+= m_end.x;
              y+= m_end.y;
              m_rect.DeflateRect(x, y,0,0);
              m_rect.InflateRect(0,0,x,y);
              this->InvalidateRect(m_rect);          //重绘文本区域
              m_title.MoveWindow(m_rect);            //移动控件
              }
              CDialog::OnMouseMove(nFlags, point);
          }

(7)处理对话框的WM_LBUTTONUP消息,结束鼠标拖动,代码如下:

          void CMoveTextDlg::OnLButtonUp(UINT nFlags, CPoint point)
          {
              m_IsDowned = FALSE;
              m_IsMove = FALSE;
              CDialog::OnLButtonUp(nFlags, point);
}

举一反三

根据本实例,读者可以:

实现跟随鼠标移动的图片。

实例121 图像水印效果

这是一个可以提高分析能力的实例

实例位置:光盘\mingrisoft\03\121

实例说明

给图片添加水印是指在图片上附加指定的文字。现在许多软件或网页中的图片都有水印,水印并不是图片本身的信息,而是在图片加载时动态附加上去的。本实例实现的是在图片上添加水印,运行程序如图3.44所示。

图3.44 图像水印效果

技术要点

本实例使用GDI+所提供的功能来实现水印的添加,GDI+是GDI图形库的一个增强版本,Visual C++可以使用这个库。它内建于Windows XP和Microsoft .NET,而对于Windows 98、Windows NT和Windows 2000,则有一个可重新发布的版本。GDI+是一个Visual C++ API。它使用Visual C++类和Visual C++方法。为了使用GDI+,必须包含(#include)<gdiplus.h>文件,并将工程链接到gdiplus.lib库,这两个文件包含在最新的Windows SDK中。

实现过程

(1)新建一个基于对话框的应用程序。

(2)在窗体上添加4个编辑框控件,分别用来显示附加水印图片的路径、水印文本、水印在图片中的x坐标与y坐标。添加两个按钮控件,分别设置其Caption属性为“字体”和“水印处理”。添加两个复选框控件,分别设置其Caption属性为“水平居中”和“垂直居中”。

(3)定义名为GetCodecClsid的全局函数,该函数用来获取指定文件类型的唯一标识。代码如下:

        int GetCodecClsid(const WCHAR* format, CLSID* pClsid)
        {
          UINT  codenum=0;
          UINT  size=0;
          ImageCodecInfo* pImageCodecInfo = NULL;
          GetImageEncodersSize(&codenum, &size);
          if(size == 0)
          return -1 ;
          pImageCodecInfo = new ImageCodecInfo[size];
          if(pImageCodecInfo == NULL)
          return -1;
          GetImageEncoders(codenum, size, pImageCodecInfo);
          for(UINT j = 0; j < codenum; ++j)
          {
          if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
          {
              *pClsid = pImageCodecInfo[j].Clsid;
                delete []pImageCodecInfo;
              return 0;
          }
          }
          delete []pImageCodecInfo;
          return -1;
        }

(4)单击窗体上的“水印处理”按钮实现对图片的水印添加。代码如下:

        void CWaterMarkerDlg::OnMarkerhandle()
        {
              //获得控件中数据
              CString szFileDir, szMarkerText, szHorX, szVerY;
              m_FileDir.GetWindowText(szFileDir);
              m_MarkerText.GetWindowText(szMarkerText);
              m_VerY.GetWindowText(szVerY);
              m_HorX.GetWindowText(szHorX);
              if (!szFileDir.IsEmpty())
              {
                  if(!m_bSetFont) //如果没有设置字体,先设置字体
                  {
                        OnBtFont();
                  }
                  try
                  {
                        //格式化坐标
                        int nX=atoi(szHorX.GetBuffer(0));
                        szHorX.ReleaseBuffer();
                        int nY=atoi(szVerY.GetBuffer(0));
                        szVerY.ReleaseBuffer();
                        //指定文件类型标识
                        CLSID clsid;
                        GetCodecClsid(L"image/jpeg",&clsid);
                        Brush*brush=new SolidBrush(Color(m_Red,m_Green,m_Blue));
                        Font*font=new Font(GetDC()->m_hDC,&m_LogFont);
                        PointF ptf;
                        int nLen=MultiByteToWideChar(CP_ACP,0,szMarkerText,-1,NULL,0);
                        int nQuality=95;
                        EncoderParameters Encoders;
                        Encoders.Count=1;
                        Encoders.Parameter[0].Guid=EncoderQuality;
                        Encoders.Parameter[0].Type=EncoderParameterValueTypeLong;
                        Encoders.Parameter[0].NumberOfValues=1;
                        Encoders.Parameter[0].Value=&nQuality;
                        //判断是否为位图
                        if(m_Strextend=="jpg"||m_Strextend=="jpeg"||m_Strextend=="bmp")
                        {
                            Bitmap*pBmp=Bitmap::FromFile(szFileDir.AllocSysString());
                            if(pBmp)
                            {
                              Graphics *pGraph = Graphics::FromImage(pBmp);
                              //获取字符串的宽度
                              PointF origin(0.0f, 0.0f);
                              RectF TextRC;
                              pGraph->MeasureString(szMarkerText.AllocSysString(), nLen,font,origin,&TextRC);
                              szMarkerText.ReleaseBuffer();
                              //设置文本位置
                              CButton* pHorButton = (CButton*)GetDlgItem(IDC_HORCENTER);
                              if (!pHorButton->GetCheck())
                              {
                                  ptf.X = nX;
                              }
                              else //水平方向居中
                              {
                                  //获取图像宽度
                                  int nWidth = pBmp->GetWidth();
                                  int nChar = TextRC.Width;
                                  //设置文本的水平方向位置
                                  ptf.X = (nWidth - nChar) / 2;
                              }
                              CButton* pVerButton = (CButton*)GetDlgItem(IDC_VERCENTER);
                              if (! pVerButton->GetCheck())
                              {
                                  ptf.Y = nY;
                              }
                              else //垂直方向居中
                              {
                                  //获取图像宽度
                                  int nHeight = pBmp->GetHeight();
                                  int nCharHeight = TextRC.Height;
                                  //设置文本的水平方向位置
                                  ptf.Y = (nHeight - nCharHeight) / 2;
                              }
                              pGraph->DrawImage(pBmp, 0, 0, pBmp->GetWidth(), pBmp->GetHeight());
                              pGraph->DrawString(szMarkerText.AllocSysString(), nLen, font, ptf, brush);
                              //创建文件夹并设置添加水印后的图片路径
                            szMarkerText.ReleaseBuffer();
                            int pos = szFileDir.ReverseFind('\\');
                            char chName[MAX_PATH] = {0};
                            strcpy(chName, szFileDir.Left(pos));
                            strcat(chName, "\\JPG");
                            CreateDirectory(chName, NULL);
                            CString JpgFile = chName;
                            JpgFile += "\\";
                            JpgFile += m_FileName;
                            //保存图片
                            pBmp->Save(JpgFile.AllocSysString(), &clsid, &Encoders);
                            JpgFile.ReleaseBuffer();
                            delete pGraph;
                        }
                        delete pBmp;
                        MessageBox("添加成功!");
                    }
              }
              catch(...)
              {
                    MessageBox("添加失败!");
              }
            }
        }

举一反三

根据本实例,读者可以:

为图片批量添加水印。