製作印章來說,主要是如何讓字均勻的顯示在弧線段上,那麼一般的印章要麼以圓或者橢圓爲底圖,不過這兩者的算法大致相同,爲了方便說明,如下就用相對簡單的圓來舉例說明,如果需要做橢圓的話,可以在我的基礎上進行擴展,因爲核心算法是一樣的,相對於圓來說,橢圓求弧長以及各個字符的位置,這兩點相對麻煩些,但是這兩者都可找到相應的數學公式。
這裏首先提一點,我這篇文章部分借鑑了codeproject的一個例子,原文可以參看如下地址。
http://www.codeproject.com/vb/net/Text_on_Path_with_VBNET.asp
(說實話,這篇文章寫得有些亂,而且對於buffer的操作近乎於瘋狂)
由於印章的實現相對於這篇文章來說,相對簡單多了,而且規律性很強,因此我自己考慮重新組織算法進行實現。
那麼實現一個印章,大致步驟如下。
1. 計算字符串總長度,以及各個字符的長度;
2. 計算出字符串的起始角度;
3. 求出每個字符的所在的點,以及相對於中心的角度;
4. 繪製每個字符。
計算字符串總長度,以及各個字符的長度
這裏需要用到“Graphics.MeasureString”和“Graphics.MeasureCharacterRanges”這兩個方法,由於前者算出來的總長度有問題,所以需要後面進行重新計算(此外,這裏我還考慮了字符最後顯示方向)。
這部分的代碼如下:
/// <summary>
/// Compute string total length and every char length
/// </summary>
/// <param name="sText"></param>
/// <param name="g"></param>
/// <param name="fCharWidth"></param>
/// <param name="fIntervalWidth"></param>
/// <returns></returns>
private float ComputeStringLength( string sText, Graphics g, float[] fCharWidth,
float fIntervalWidth,
Char_Direction Direction )
{
// Init string format
StringFormat sf = new StringFormat();
sf.Trimming = StringTrimming.None;
sf.FormatFlags = StringFormatFlags.NoClip | StringFormatFlags.NoWrap
| StringFormatFlags.LineLimit;
// Measure whole string length
SizeF size = g.MeasureString( sText, _font, (int)_font.Style );
RectangleF rect = new RectangleF( 0f,0f, size.Width, size.Height );
// Measure every character size
CharacterRange[] crs = new CharacterRange[sText.Length];
for( int i = 0; i < sText.Length; i++ )
crs[i] = new CharacterRange( i, 1 );
// Reset string format
sf.FormatFlags = StringFormatFlags.NoClip;
sf.SetMeasurableCharacterRanges( crs );
sf.Alignment = StringAlignment.Near;
// Get every character size
Region[] regs = g.MeasureCharacterRanges( sText,
_font, rect, sf );
// Re-compute whole string length with space interval width
float fTotalWidth = 0f;
for( int i = 0; i < regs.Length; i++ )
{
if( Direction == Char_Direction.Center || Direction == Char_Direction.OutSide )
fCharWidth[i] = regs[i].GetBounds( g ).Width;
else
fCharWidth[i] = regs[i].GetBounds( g ).Height;
fTotalWidth += fCharWidth[i] + fIntervalWidth;
}
fTotalWidth -= fIntervalWidth;//Remove the last interval width
return fTotalWidth;
}
計算出字符串的起始角度
爲了更好地開展文章,那麼首先說說在我這篇文章中,座標的度數位置。詳情參看如下圖示。
對於圖形角度分佈有個概念後,那麼對於整個字符串所跨的弧度以及起始弧度的計算,就相對比較簡單了。具體如下:
// Compute arc's start-angle and end-angle
double fStartAngle, fSweepAngle;
fSweepAngle = fTotalWidth * 360 / ( _rectcircle.Width * Math.PI );
fStartAngle = 270 - fSweepAngle / 2;
求出每個字符的所在的點,以及相對於中心的角度
這一部分相對要麻煩些,大致步驟如下。
1. 通過字符長度,求出字符所跨的弧度;
2. 根據字符所跨的弧度,以及字符起始位置,算出字符的中心位置所對應的角度;
3. 由於相對中心的角度已知,根據三角公式很容易算出字符所在弧上的點,如下圖所示;
4. 根據字符長度以及間隔距離,算出下一個字符的起始角度;
5. 重複1直至整個字符串結束。
那麼這部分的具體代碼如下。
/// <summary>
/// Compute every char position
/// </summary>
/// <param name="CharWidth"></param>
/// <param name="recChars"></param>
/// <param name="CharAngle"></param>
/// <param name="StartAngle"></param>
private void ComputeCharPos(
float[] CharWidth,
PointF[] recChars,
double[] CharAngle,
double StartAngle )
{
double fSweepAngle, fCircleLength;
//Compute the circumference
fCircleLength = _rectcircle.Width * Math.PI;
for( int i = 0; i < CharWidth.Length; i++ )
{
//Get char sweep angle
fSweepAngle = CharWidth[i] * 360 / fCircleLength;
//Set point angle
CharAngle[i] = StartAngle + fSweepAngle / 2;
//Get char position
if( CharAngle[i] < 270f )
recChars[i] = new PointF(
_rectcircle.X + _rectcircle.Width / 2
-(float)( _rectcircle.Width / 2 *
Math.Sin( Math.Abs( CharAngle[i] - 270 ) * Math.PI / 180 ) ) ,
_rectcircle.Y + _rectcircle.Width / 2
-(float)( _rectcircle.Width / 2 * Math.Cos(
Math.Abs( CharAngle[i] - 270 ) * Math.PI / 180 ) ) );
else
recChars[i] = new PointF(
_rectcircle.X + _rectcircle.Width / 2
+(float)( _rectcircle.Width / 2 *
Math.Sin( Math.Abs( CharAngle[i] - 270 ) * Math.PI / 180 ) ) ,
_rectcircle.Y + _rectcircle.Width / 2
-(float)( _rectcircle.Width / 2 * Math.Cos(
Math.Abs( CharAngle[i] - 270 ) * Math.PI / 180 ) ) );
//Get total sweep angle with interval space
fSweepAngle = ( CharWidth[i] + _letterspace ) * 360 / fCircleLength;
StartAngle += fSweepAngle;
}
}
繪製每個字符
由於每個字符所在的點以及相對於圓心的角度都已經計算出來,那麼進行繪製就相對簡單多了,這裏只是通過Matrix來轉換一下座標而已。
具體代碼如下。
/// <summary>
/// Draw every rotated character
/// </summary>
/// <param name="g"></param>
/// <param name="_text"></param>
/// <param name="_angle"></param>
/// <param name="_PointCenter"></param>
private void DrawRotatedText( Graphics g, string _text, float _angle, PointF _PointCenter )
{
// Init format
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
// Create graphics path
GraphicsPath gp = new GraphicsPath( System.Drawing.Drawing2D.FillMode.Winding );
int x = (int)_PointCenter.X;
int y = (int)_PointCenter.Y;
// Add string
gp.AddString( _text, _font.FontFamily, (int)_font.Style,
_font.Size, new Point( x, y ), sf );
// Rotate string and draw it
Matrix m = new Matrix();
m.RotateAt( _angle, new PointF( x,y ) );
g.Transform = m;
g.DrawPath( new Pen( _color), gp );
g.FillPath( new SolidBrush( _fillcolor ), gp );
}
以上就是繪製印章的核心算法。對於這個類的調用,如下即可。
TextOnSeal _top = new TextOnSeal();
_top.TextFont = new Font("宋體", 16, FontStyle.Regular);
_top.FillColor = Color.Red;
_top.ColorTOP = Color.Black;
_top.Text = "中華人民共和國";
_top.BaseString = "愚翁專用章";
_top.ShowPath = true;
_top.LetterSpace = 20;
_top.SealSize = 180;
_top.CharDirection = Char_Direction.Center;
_top.SetIndent( 20 );
Graphics g = this.CreateGraphics();
g.DrawImage( _top.TextOnPathBitmap(), 0, 0 );
_top.CharDirection = Char_Direction.ClockWise;
g.DrawImage( _top.TextOnPathBitmap(), 180, 0 );
_top.CharDirection = Char_Direction.AntiClockWise;
g.DrawImage( _top.TextOnPathBitmap(), 0, 180 );
_top.SetIndent( 20 );
_top.CharDirection = Char_Direction.OutSide;
g.DrawImage( _top.TextOnPathBitmap(), 180, 180 );
通過如上的代碼,可以得到如下的效果。
其實如果做印章來說,還有很多地方需要細化,那麼如果網友對此有興趣,可以在我的基礎上進行擴展,在此我就不一一述說。
如下是整個類的完整代碼。
//--------------------------- TextOnSeal class ---------------------------------------
//------------------------------------------------------------------------------------
//---File: TextOnSeal
//---Description: The class file to create seal bitmap with text
//---Author: Knight
//---Date: Nov.3, 2006
//------------------------------------------------------------------------------------
//---------------------------{TextOnSeal class}---------------------------------------
namespace Seal
{
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Diagnostics;
/// <summary>
/// Summary description for TextOnSeal.
/// </summary>
public class TextOnSeal
{
private string _text;
private Font _font;
private Color _pathcolor = Color.Red;
private Color _color = Color.Black;
private Color _fillcolor = Color.Black;
private int _letterspace = 10;
private bool _showpath = true;
private Rectangle _rectcircle;
private Rectangle _rect;
private int _intentlength = 10;
private Char_Direction _chardirect = Char_Direction.Center;
private int _degree = 90;
private string _basestring;
#region Class_Properties
public Char_Direction CharDirection
{
get{ return _chardirect;}
set{
if( _chardirect != value )
{
_chardirect = value;
switch( _chardirect )
{
case Char_Direction.Center:
_degree = 90;
break;
case Char_Direction.ClockWise:
_degree = 0;
break;
case Char_Direction.OutSide:
_degree = -90;
break;
case Char_Direction.AntiClockWise:
_degree = 180;
break;
}
}
}
}
public string BaseString
{
get{ return _basestring;}
set{ _basestring = value; }
}
public string Text
{
get{ return _text;}
set{ _text = value;}
}
public Font TextFont
{
get{ return _font;}
set{ _font = value;}
}
public Color PathColor
{
get{ return _pathcolor;}
set{ _pathcolor = value; }
}
public Color ColorTOP
{
get{ return _color;}
set{ _color = value;}
}
public Color FillColor
{
get{ return _fillcolor;}
set{ _fillcolor = value;}
}
public int LetterSpace
{
get{ return _letterspace;}
set{ _letterspace = value;}
}
public bool ShowPath
{
get{ return _showpath;}
set{ _showpath = value;}
}
public int SealSize
{
set{
_rect = new Rectangle( 0, 0, value, value );
_rectcircle = new Rectangle(
new Point( _rect.X + _intentlength, _rect.Y + _intentlength ),
new Size( _rect.Width - 2 * _intentlength, _rect.Height - 2 * _intentlength ) ) ;
}
}
#endregion {Class_Properties}
public void SetIndent( int IntentLength )
{
_intentlength = IntentLength;
_rectcircle = new Rectangle( _intentlength, _intentlength,
_rect.Width - _intentlength * 2, _rect.Height - _intentlength * 2 );
}
public TextOnSeal()
{
//
// TODO: Add constructor logic here
//
}
public Bitmap TextOnPathBitmap(
Rectangle rectCircle,
string strText,
Font fntText,
Color clrColor,
Color clrFill,
int nPercentage )
{
_rect = rectCircle;
_rectcircle = new Rectangle(
new Point( _rect.X + _intentlength, _rect.Y + _intentlength ),
new Size( _rect.Width - 2 * _intentlength, _rect.Height - 2 * _intentlength ) ) ;
_text = strText;
_font = fntText;
_color = clrColor;
_fillcolor = clrFill;
_letterspace = nPercentage;
return TextOnPathBitmap();
}
/// <summary>
/// Compute string total length and every char length
/// </summary>
/// <param name="sText"></param>
/// <param name="g"></param>
/// <param name="fCharWidth"></param>
/// <param name="fIntervalWidth"></param>
/// <returns></returns>
private float ComputeStringLength( string sText, Graphics g, float[] fCharWidth,
float fIntervalWidth,
Char_Direction Direction )
{
// Init string format
StringFormat sf = new StringFormat();
sf.Trimming = StringTrimming.None;
sf.FormatFlags = StringFormatFlags.NoClip | StringFormatFlags.NoWrap
| StringFormatFlags.LineLimit;
// Measure whole string length
SizeF size = g.MeasureString( sText, _font, (int)_font.Style );
RectangleF rect = new RectangleF( 0f,0f, size.Width, size.Height );
// Measure every character size
CharacterRange[] crs = new CharacterRange[sText.Length];
for( int i = 0; i < sText.Length; i++ )
crs[i] = new CharacterRange( i, 1 );
// Reset string format
sf.FormatFlags = StringFormatFlags.NoClip;
sf.SetMeasurableCharacterRanges( crs );
sf.Alignment = StringAlignment.Near;
// Get every character size
Region[] regs = g.MeasureCharacterRanges( sText,
_font, rect, sf );
// Re-compute whole string length with space interval width
float fTotalWidth = 0f;
for( int i = 0; i < regs.Length; i++ )
{
if( Direction == Char_Direction.Center || Direction == Char_Direction.OutSide )
fCharWidth[i] = regs[i].GetBounds( g ).Width;
else
fCharWidth[i] = regs[i].GetBounds( g ).Height;
fTotalWidth += fCharWidth[i] + fIntervalWidth;
}
fTotalWidth -= fIntervalWidth;//Remove the last interval width
return fTotalWidth;
}
/// <summary>
/// Compute every char position
/// </summary>
/// <param name="CharWidth"></param>
/// <param name="recChars"></param>
/// <param name="CharAngle"></param>
/// <param name="StartAngle"></param>
private void ComputeCharPos(
float[] CharWidth,
PointF[] recChars,
double[] CharAngle,
double StartAngle )
{
double fSweepAngle, fCircleLength;
//Compute the circumference
fCircleLength = _rectcircle.Width * Math.PI;
for( int i = 0; i < CharWidth.Length; i++ )
{
//Get char sweep angle
fSweepAngle = CharWidth[i] * 360 / fCircleLength;
//Set point angle
CharAngle[i] = StartAngle + fSweepAngle / 2;
//Get char position
if( CharAngle[i] < 270f )
recChars[i] = new PointF(
_rectcircle.X + _rectcircle.Width / 2
-(float)( _rectcircle.Width / 2 *
Math.Sin( Math.Abs( CharAngle[i] - 270 ) * Math.PI / 180 ) ) ,
_rectcircle.Y + _rectcircle.Width / 2
-(float)( _rectcircle.Width / 2 * Math.Cos(
Math.Abs( CharAngle[i] - 270 ) * Math.PI / 180 ) ) );
else
recChars[i] = new PointF(
_rectcircle.X + _rectcircle.Width / 2
+(float)( _rectcircle.Width / 2 *
Math.Sin( Math.Abs( CharAngle[i] - 270 ) * Math.PI / 180 ) ) ,
_rectcircle.Y + _rectcircle.Width / 2
-(float)( _rectcircle.Width / 2 * Math.Cos(
Math.Abs( CharAngle[i] - 270 ) * Math.PI / 180 ) ) );
//Get total sweep angle with interval space
fSweepAngle = ( CharWidth[i] + _letterspace ) * 360 / fCircleLength;
StartAngle += fSweepAngle;
}
}
/// <summary>
/// Generate seal bitmap
/// </summary>
/// <returns></returns>
public Bitmap TextOnPathBitmap()
{
// Create bitmap and graphics
Bitmap bit = new Bitmap( _rect.Width, _rect.Height );
Graphics g = Graphics.FromImage( bit );
// Compute string length in graphics
float[] fCharWidth = new float[_text.Length];
float fTotalWidth = ComputeStringLength( _text, g, fCharWidth,
_letterspace, _chardirect );
// Compute arc's start-angle and end-angle
double fStartAngle, fSweepAngle;
fSweepAngle = fTotalWidth * 360 / ( _rectcircle.Width * Math.PI );
fStartAngle = 270 - fSweepAngle / 2;
// Compute every character's position and angle
PointF[] pntChars = new PointF[ _text.Length ];
double[] fCharAngle = new double[ _text.Length ];
ComputeCharPos( fCharWidth, pntChars, fCharAngle, fStartAngle );
g.SmoothingMode = SmoothingMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
DrawSealBase( g );
// Draw every character
for( int i = 0; i < _text.Length; i++ )
DrawRotatedText( g, _text[i].ToString(), (float)(fCharAngle[i] + _degree), pntChars[i] );
g.Dispose();
// Return bitmap
return bit;
}
/// <summary>
/// Draw seal base
/// </summary>
/// <param name="g"></param>
private void DrawSealBase( Graphics g )
{
// Draw background
g.FillRectangle( Brushes.Black, _rect );
g.FillEllipse( new SolidBrush( _fillcolor ),
new Rectangle( 1,1, _rect.Width - 2, _rect.Height - 2 ) );
g.FillEllipse( Brushes.White,
new Rectangle( 4, 4, _rect.Width - 8, _rect.Height - 8 ) );
// Draw start signal
StringFormat sf = new StringFormat();
string strStar = "★";
Font fnt = new Font( _font.FontFamily, _font.Size * 3 );
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
SizeF siz = g.MeasureString( strStar, fnt );
g.DrawString( strStar, fnt, new SolidBrush( _fillcolor ),
new RectangleF( _rect.Width / 2 - siz.Width / 2,
_rect.Height / 2 - siz.Height/ 2,
siz.Width, siz.Height ), sf );
// Draw base string
float[] fCharWidth = new float[_basestring.Length];
float fTotalWidths = ComputeStringLength( _basestring, g, fCharWidth, 0,
Char_Direction.Center );
float fLeftPos = ( _rect.Width - fTotalWidths ) / 2;
PointF pt;
for( int i = 0; i < _basestring.Length; i++ )
{
pt = new PointF( fLeftPos + fCharWidth[i] / 2,
_rect.Height / 2 + siz.Height / 2 + 10 );
DrawRotatedText( g, _basestring[i].ToString(), 0, pt );
fLeftPos += fCharWidth[i];
}
}
/// <summary>
/// Draw every rotated character
/// </summary>
/// <param name="g"></param>
/// <param name="_text"></param>
/// <param name="_angle"></param>
/// <param name="_PointCenter"></param>
private void DrawRotatedText( Graphics g, string _text, float _angle, PointF _PointCenter )
{
// Init format
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
// Create graphics path
GraphicsPath gp = new GraphicsPath( System.Drawing.Drawing2D.FillMode.Winding );
int x = (int)_PointCenter.X;
int y = (int)_PointCenter.Y;
// Add string
gp.AddString( _text, _font.FontFamily, (int)_font.Style,
_font.Size, new Point( x, y ), sf );
// Rotate string and draw it
Matrix m = new Matrix();
m.RotateAt( _angle, new PointF( x,y ) );
g.Transform = m;
g.DrawPath( new Pen( _color), gp );
g.FillPath( new SolidBrush( _fillcolor ), gp );
}
}
public enum Char_Direction
{
Center = 0,
OutSide = 1,
ClockWise = 2,
AntiClockWise = 3,
}
}