如何将弯曲的文本渲染到位图中?

5

我目前正在动态创建一个位图,并使用该位图的图形对象在其上绘制字符串,如下所示:

System.Drawing.Graphics graph = System.Drawing.Graphics.FromImage(bmp);
graph.DrawString(text, font, brush, new System.Drawing.Point(0, 0));

这将返回一个矩形位图,其中字符串从左到右直接书写。 我还想能够以彩虹的形状绘制字符串。 我该怎么做?


请使用此链接:http://csharphelper.com/blog/2016/01/draw-text-on-a-curve-in-c/ - M.R.T
4个回答

19

我最近遇到了这个问题(我正在为打印到CD上的文本进行渲染),所以这是我的解决方案:

private void DrawCurvedText(Graphics graphics, string text, Point centre, float distanceFromCentreToBaseOfText, float radiansToTextCentre, Font font, Brush brush)
{
    // Circumference for use later
    var circleCircumference = (float)(Math.PI * 2 * distanceFromCentreToBaseOfText);

    // Get the width of each character
    var characterWidths = GetCharacterWidths(graphics, text, font).ToArray();

    // The overall height of the string
    var characterHeight = graphics.MeasureString(text, font).Height;

    var textLength = characterWidths.Sum();

    // The string length above is the arc length we'll use for rendering the string. Work out the starting angle required to 
    // centre the text across the radiansToTextCentre.
    float fractionOfCircumference = textLength / circleCircumference;

    float currentCharacterRadians = radiansToTextCentre + (float)(Math.PI * fractionOfCircumference);

    for (int characterIndex = 0; characterIndex < text.Length; characterIndex++)
    {
        char @char = text[characterIndex];

        // Polar to cartesian
        float x = (float)(distanceFromCentreToBaseOfText * Math.Sin(currentCharacterRadians));
        float y = -(float)(distanceFromCentreToBaseOfText * Math.Cos(currentCharacterRadians));

        using (GraphicsPath characterPath = new GraphicsPath())
        {
            characterPath.AddString(@char.ToString(), font.FontFamily, (int)font.Style, font.Size, Point.Empty,
                                    StringFormat.GenericTypographic);

            var pathBounds = characterPath.GetBounds();

            // Transformation matrix to move the character to the correct location. 
            // Note that all actions on the Matrix class are prepended, so we apply them in reverse.
            var transform = new Matrix();

            // Translate to the final position
            transform.Translate(centre.X + x, centre.Y + y);

            // Rotate the character
            var rotationAngleDegrees = currentCharacterRadians * 180F / (float)Math.PI - 180F;
            transform.Rotate(rotationAngleDegrees);

            // Translate the character so the centre of its base is over the origin
            transform.Translate(-pathBounds.Width / 2F, -characterHeight);

            characterPath.Transform(transform);

            // Draw the character
            graphics.FillPath(brush, characterPath);
        }

        if (characterIndex != text.Length - 1)
        {
            // Move "currentCharacterRadians" on to the next character
            var distanceToNextChar = (characterWidths[characterIndex] + characterWidths[characterIndex + 1]) / 2F;
            float charFractionOfCircumference = distanceToNextChar / circleCircumference;
            currentCharacterRadians -= charFractionOfCircumference * (float)(2F * Math.PI);
        }
    }
}

private IEnumerable<float> GetCharacterWidths(Graphics graphics, string text, Font font)
{
    // The length of a space. Necessary because a space measured using StringFormat.GenericTypographic has no width.
    // We can't use StringFormat.GenericDefault for the characters themselves, as it adds unwanted spacing.
    var spaceLength = graphics.MeasureString(" ", font, Point.Empty, StringFormat.GenericDefault).Width;

    return text.Select(c => c == ' ' ? spaceLength : graphics.MeasureString(c.ToString(), font, Point.Empty, StringFormat.GenericTypographic).Width);
}

screenshot of curved text in program output


这里有一个小观察,文本是逆时针旋转的。是否可能改为顺时针旋转呢? - Echilon
3
嗨@Echilon,您可以通过更改几行代码来反转曲线:在计算“rotationAngleDegrees”时删除“-180F”,并在分配“currentCharacterRadians”的两个位置上,将减法更改为加法,反之亦然! - Simon MᶜKenzie
@Rasool,我认为最好的方法是使用WPF。正如这个答案所提到的,GDI+中没有一种方法可以沿着路径渲染字符串。还有一个Charles Petzold的文章,也在另一个答案中提到了。 - Simon MᶜKenzie
@Simon,有没有办法用svgcanvas来实现这个?实际上,我是用svg textPath来实现的,但它不能处理从右到左的语言,并且会将字符分开! - Rasool Ghafari
1
@Rasool,恐怕这超出了我的专业范围!你应该提出一个新问题,解释一下RTL问题。祝你好运! - Simon MᶜKenzie
显示剩余2条评论

2

我需要用C#回答这个问题,并且找不到任何可供参考的示例:

这个解决方案需要大量的数学知识来解决。简而言之,它需要一组点(2D向量),将它们对齐到基线上并在样条曲线上弯曲这些点。代码足够快以实时更新,并能处理循环等情况。

为了简化操作,解决方案使用GraphicsPath.AddString将文本转换为向量,并使用GraphicsPath.PathPoints/PathTypes获取这些点,但是您也可以使用相同的函数弯曲任何形状或甚至位图。(我不建议实时处理4k位图)。

代码包含一个简单的“Paint”函数,后面是Spline类。Paint方法中使用GraphicsPath使代码易于理解。ClickedPoints是您想将文本弯曲的点的数组。我使用List,因为它在Mouse事件中添加到其中,如果您已经知道这些点,则可以使用数组。

public void Paint(System.Drawing.Graphics g)
{
    List<System.Drawing.Point> clickedPoints = new List<System.Drawing.Point>();
    Additional.Math.Beziers.BezierSplineCubic2D _Spline = new Additional.Math.Beziers.BezierSplineCubic2D();

    // Create the spline, exit if no points to bend around.
    System.Drawing.PointF[] cd = Additional.Math.Beziers.BezierSplineCubic2D.CreateCurve(ClickedPoints.ToArray(), 0, ClickedPoints.Count, 0);
    _Spline = new Additional.Math.Beziers.BezierSplineCubic2D(cd);
    if (_Spline.Beziers == null || _Spline.Length == 0) return;

    // Start Optional: Remove if you only want the bent object
    // Draw the spline curve the text will be put onto using inbuilt GDI+ calls
    g.DrawCurve(System.Drawing.Pens.Blue, clickedPoints.ToArray());
    // draw the control point data for the curve
    for (int i = 0; i < cd.Length; i++)
    {
        g.DrawRectangle(System.Drawing.Pens.Green, cd[i].X - 2F, cd[i].Y - 2F, 4F, 4F);
    }
    // End Optional:

    // Turn the text into points that can be bent - if no points then exit:
    System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
    path.AddString("Lorem ipsum dolor", new System.Drawing.FontFamily("Arial"), 0, 12.0F, new System.Drawing.Point(0, 0), new System.Drawing.StringFormat() { Alignment = System.Drawing.StringAlignment.Near });
    textBounds = path.GetBounds();
    curvedData = (System.Drawing.PointF[])path.PathPoints.Clone();
    curvedTypes = (byte[])path.PathTypes.Clone();
    dataLength = curvedData.Length;
    if (dataLength == 0) return;

    // Bend the shape(text) around the path (Spline)
    _Spline.BendShapeToSpline(textBounds, dataLength, ref curvedData, ref curvedTypes);


    // draw the transformed text path
    System.Drawing.Drawing2D.GraphicsPath textPath = new System.Drawing.Drawing2D.GraphicsPath(curvedData, curvedTypes);
    g.DrawPath(System.Drawing.Pens.Black, textPath);
}

现在来介绍样条类:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Additional.Math
{
namespace Vectors
{
    public struct Vector2DFloat
    {
        public float X;
        public float Y;
        public void SetXY(float x, float y)
        {
            X = x;
            Y = y;
        }
        public static Vector2DFloat Lerp(Vector2DFloat v0, Vector2DFloat v1, float t)
        {
            return v0 + (v1 - v0) * t;
        }

        public Vector2DFloat(Vector2DFloat value)
        {
            this.X = value.X;
            this.Y = value.Y;
        }
        public Vector2DFloat(float x, float y)
        {
            this.X = x;
            this.Y = y;
        }

        public Vector2DFloat Rotate90Degrees(bool positiveRotation)
        {
            return positiveRotation ? new Vector2DFloat(-Y, X) : new Vector2DFloat(Y, -X);
        }
        public Vector2DFloat Normalize()
        {
            float magnitude = (float)System.Math.Sqrt(X * X + Y * Y);
            return new Vector2DFloat(X / magnitude, Y / magnitude);
        }
        public float Distance(Vector2DFloat target)
        {
            return (float)System.Math.Sqrt((X - target.X) * (X - target.X) + (Y - target.Y) * (Y - target.Y));
        }
        public float DistanceSquared(Vector2DFloat target)
        {
            return (X - target.X) * (X - target.X) + (Y - target.Y) * (Y - target.Y);
        }
        public double DistanceTo(Vector2DFloat target)
        {
            return System.Math.Sqrt(System.Math.Pow(target.X - X, 2F) + System.Math.Pow(target.Y - Y, 2F));
        }

        public System.Drawing.PointF ToPointF()
        {
            return new System.Drawing.PointF(X, Y);
        }
        public Vector2DFloat(System.Drawing.PointF value)
        {
            this.X = value.X;
            this.Y = value.Y;
        }
        public static implicit operator Vector2DFloat(System.Drawing.PointF value)
        {
            return new Vector2DFloat(value);
        }

        public static Vector2DFloat operator +(Vector2DFloat first, Vector2DFloat second)
        {
            return new Vector2DFloat(first.X + second.X, first.Y + second.Y);
        }
        public static Vector2DFloat operator -(Vector2DFloat first, Vector2DFloat second)
        {
            return new Vector2DFloat(first.X - second.X, first.Y - second.Y);
        }
        public static Vector2DFloat operator *(Vector2DFloat first, float second)
        {
            return new Vector2DFloat(first.X * second, first.Y * second);
        }
        public static Vector2DFloat operator *(float first, Vector2DFloat second)
        {
            return new Vector2DFloat(second.X * first, second.Y * first);
        }
        public static Vector2DFloat operator *(Vector2DFloat first, int second)
        {
            return new Vector2DFloat(first.X * second, first.Y * second);
        }
        public static Vector2DFloat operator *(int first, Vector2DFloat second)
        {
            return new Vector2DFloat(second.X * first, second.Y * first);
        }
        public static Vector2DFloat operator *(Vector2DFloat first, double second)
        {
            return new Vector2DFloat((float)(first.X * second), (float)(first.Y * second));
        }
        public static Vector2DFloat operator *(double first, Vector2DFloat second)
        {
            return new Vector2DFloat((float)(second.X * first), (float)(second.Y * first));
        }
        public override bool Equals(object obj)
        {
            return this.Equals((Vector2DFloat)obj);
        }
        public bool Equals(Vector2DFloat p)
        {
            // If parameter is null, return false.
            if (p == null)
            {
                return false;
            }

            // Optimization for a common success case.
            if (this == p)
            {
                return true;
            }

            // If run-time types are not exactly the same, return false.
            if (this.GetType() != p.GetType())
            {
                return false;
            }

            // Return true if the fields match.
            // Note that the base class is not invoked because it is
            // System.Object, which defines Equals as reference equality.
            return (X == p.X) && (Y == p.Y);
        }
        public override int GetHashCode()
        {
            return (int)(System.Math.Round(X + Y, 4) * 10000);
        }
        public static bool operator ==(Vector2DFloat first, Vector2DFloat second)
        {
            // Check for null on left side.
            if (first == null)
            {
                if (second == null)
                {
                    // null == null = true.
                    return true;
                }

                // Only the left side is null.
                return false;
            }
            // Equals handles case of null on right side.
            return first.Equals(second);
        }
        public static bool operator !=(Vector2DFloat first, Vector2DFloat second)
        {
            return !(first == second);
        }
    }
}
namespace Beziers
{
    public struct BezierCubic2D
    {
        public Vectors.Vector2DFloat P0;
        public Vectors.Vector2DFloat P1;
        public Vectors.Vector2DFloat P2;
        public Vectors.Vector2DFloat P3;
        public int ArcLengthDivisionCount;
        public List<float> ArcLengths { get { if (_ArcLengths.Count == 0) CalculateArcLength(); return _ArcLengths; } }
        public float ArcLength { get { if (_ArcLength == 0.0F) CalculateArcLength(); return _ArcLength; } }

        private Vectors.Vector2DFloat A;
        private Vectors.Vector2DFloat B;
        private Vectors.Vector2DFloat C;
        private List<float> _ArcLengths;
        private float _ArcLength;

        public BezierCubic2D(Vectors.Vector2DFloat p0, Vectors.Vector2DFloat p1, Vectors.Vector2DFloat p2, Vectors.Vector2DFloat p3)
        {
            P0 = p0;
            P1 = p1;
            P2 = p2;
            P3 = p3;

            // vt = At^3 + Bt^2 + Ct + p0
            A = P3 - 3 * P2 + 3 * P1 - P0;
            B = 3 * P2 - 6 * P1 + 3 * P0;
            C = 3 * P1 - 3 * P0;
            ArcLengthDivisionCount = 100;
            _ArcLengths = new List<float>();
            _ArcLength = 0.0F;
        }
        public BezierCubic2D(System.Drawing.PointF p0, System.Drawing.PointF p1, System.Drawing.PointF p2, System.Drawing.PointF p3)
        {
            P0 = p0;
            P1 = p1;
            P2 = p2;
            P3 = p3;

            // vt = At^3 + Bt^2 + Ct + p0
            A = P3 - 3 * P2 + 3 * P1 - P0;
            B = 3 * P2 - 6 * P1 + 3 * P0;
            C = 3 * P1 - 3 * P0;
            ArcLengthDivisionCount = 100;
            _ArcLengths = new List<float>();
            _ArcLength = 0.0F;
        }
        public BezierCubic2D(float p0X, float p0Y, float p1X, float p1Y, float p2X, float p2Y, float p3X, float p3Y)
        {
            P0 = new Vectors.Vector2DFloat(p0X, p0Y);
            P1 = new Vectors.Vector2DFloat(p1X, p1Y);
            P2 = new Vectors.Vector2DFloat(p2X, p2Y);
            P3 = new Vectors.Vector2DFloat(p3X, p3Y);

            // vt = At^3 + Bt^2 + Ct + p0
            A = P3 - 3 * P2 + 3 * P1 - P0;
            B = 3 * P2 - 6 * P1 + 3 * P0;
            C = 3 * P1 - 3 * P0;
            ArcLengthDivisionCount = 100;
            _ArcLengths = new List<float>();
            _ArcLength = 0.0F;
        }

        public Vectors.Vector2DFloat PointOnCurve(float t)
        {
            return A * System.Math.Pow(t, 3) + B * System.Math.Pow(t, 2) + C * t + P0;
        }
        public Vectors.Vector2DFloat PointOnCurveGeometric(float t)
        {
            Vectors.Vector2DFloat p4 = Vectors.Vector2DFloat.Lerp(P0, P1, t);
            Vectors.Vector2DFloat p5 = Vectors.Vector2DFloat.Lerp(P1, P2, t);
            Vectors.Vector2DFloat p6 = Vectors.Vector2DFloat.Lerp(P2, P3, t);
            Vectors.Vector2DFloat p7 = Vectors.Vector2DFloat.Lerp(p4, p5, t);
            Vectors.Vector2DFloat p8 = Vectors.Vector2DFloat.Lerp(p5, p6, t);
            return Vectors.Vector2DFloat.Lerp(p7, p8, t);
        }
        public Vectors.Vector2DFloat PointOnCurveTangent(float t)
        {
            return 3 * A * System.Math.Pow(t, 2) + 2 * B * t + C;
        }
        public Vectors.Vector2DFloat PointOnCurvePerpendicular(float t, bool positiveRotation)
        {
            return (3 * A * System.Math.Pow(t, 2) + 2 * B * t + C).Rotate90Degrees(positiveRotation).Normalize() * 10F + PointOnCurve(t);
        }
        public Vectors.Vector2DFloat PointOnCurvePerpendicular(float t, bool positiveRotation, float pointHeight)
        {
            return (3 * A * System.Math.Pow(t, 2) + 2 * B * t + C).Rotate90Degrees(positiveRotation).Normalize() * pointHeight + PointOnCurve(t);
        }
        public float FindTAtPointOnBezier(float u)
        {
            float t;
            int index = _ArcLengths.BinarySearch(u);
            if (index >= 0)
                t = index / (float)(_ArcLengths.Count - 1);
            else if (index * -1 >= _ArcLengths.Count)
                t = 1;
            else if (index == 0)
                t = 0;
            else
            {
                index *= -1;
                float lengthBefore = _ArcLengths[index - 1];
                float lengthAfter = _ArcLengths[index];
                float segmentLength = lengthAfter - lengthBefore;

                float segmentFraction = (u - lengthBefore) / segmentLength;

                // add that fractional amount to t 
                t = (index + segmentFraction) / (float)(_ArcLengths.Count - 1);
            }
            return t;
        }

        private void CalculateArcLength()
        {
            // calculate Arc Length through successive approximation. Use the squared version as it is faster.
            _ArcLength = 0.0F;
            int arrayMax = ArcLengthDivisionCount + 1;
            _ArcLengths = new List<float>(arrayMax)
            {
                0.0F
            };

            Vectors.Vector2DFloat prior = P0, current;
            for (int i = 1; i < arrayMax; i++)
            {
                current = PointOnCurve(i / (float)ArcLengthDivisionCount);
                _ArcLength += current.Distance(prior);
                _ArcLengths.Add(_ArcLength);
                prior = current;
            }
        }

        public override bool Equals(object obj)
        {
            return this.Equals((BezierCubic2D)obj);
        }
        public bool Equals(BezierCubic2D p)
        {
            // If parameter is null, return false.
            if (p == null)
            {
                return false;
            }

            // Optimization for a common success case.
            if (this == p)
            {
                return true;
            }

            // If run-time types are not exactly the same, return false.
            if (this.GetType() != p.GetType())
            {
                return false;
            }

            // Return true if the fields match.
            // Note that the base class is not invoked because it is
            // System.Object, which defines Equals as reference equality.
            return (P0 == p.P0) && (P1 == p.P1) && (P2 == p.P2) && (P3 == p.P3);
        }
        public override int GetHashCode()
        {
            return P0.GetHashCode() + P1.GetHashCode() + P2.GetHashCode() + P3.GetHashCode() % int.MaxValue;
        }
        public static bool operator ==(BezierCubic2D first, BezierCubic2D second)
        {
            // Check for null on left side.
            if (first == null)
            {
                if (second == null)
                {
                    // null == null = true.
                    return true;
                }

                // Only the left side is null.
                return false;
            }
            // Equals handles case of null on right side.
            return first.Equals(second);
        }
        public static bool operator !=(BezierCubic2D first, BezierCubic2D second)
        {
            return !(first == second);
        }
    }
    public struct BezierSplineCubic2D
    {
        public BezierCubic2D[] Beziers;

        public BezierCubic2D this[int index] { get { return Beziers[index]; } }
        public int Length { get { return Beziers.Length; } }
        public List<float> ArcLengths { get { if (_ArcLengths.Count == 0) CalculateArcLength(); return _ArcLengths; } }
        public float ArcLength { get { if (_ArcLength == 0.0F) CalculateArcLength(); return _ArcLength; } }

        private List<float> _ArcLengths;
        private float _ArcLength;

        public BezierSplineCubic2D(Vectors.Vector2DFloat[] source)
        {
            if (source == null || source.Length < 4 || (source.Length - 4) % 3 != 0) { Beziers = null; _ArcLength = 0.0F; _ArcLengths = new List<float>(); return; }
            int length = ((source.Length - 4) / 3) + 1;
            Beziers = new BezierCubic2D[length];
            Beziers[0] = new BezierCubic2D(source[0], source[1], source[2], source[3]);
            for (int i = 1; i < length; i++)
                Beziers[i] = new BezierCubic2D(source[(i * 3)], source[(i * 3) + 1], source[(i * 3) + 2], source[(i * 3) + 3]);
            _ArcLength = 0.0F;
            _ArcLengths = new List<float>();
        }
        public BezierSplineCubic2D(System.Drawing.PointF[] source)
        {
            if (source == null || source.Length < 4 || (source.Length - 4) % 3 != 0) { Beziers = null; _ArcLength = 0.0F; _ArcLengths = new List<float>(); return; }
            int length = ((source.Length - 4) / 3) + 1;
            Beziers = new BezierCubic2D[length];
            Beziers[0] = new BezierCubic2D(source[0], source[1], source[2], source[3]);
            for (int i = 1; i < length; i++)
                Beziers[i] = new BezierCubic2D(source[(i * 3)], source[(i * 3) + 1], source[(i * 3) + 2], source[(i * 3) + 3]);
            _ArcLength = 0.0F;
            _ArcLengths = new List<float>();
        }
        public BezierSplineCubic2D(System.Drawing.Point[] source)
        {
            if (source == null || source.Length < 4 || (source.Length - 4) % 3 != 0) { Beziers = null; _ArcLength = 0.0F; _ArcLengths = new List<float>(); return; }
            int length = ((source.Length - 4) / 3) + 1;
            Beziers = new BezierCubic2D[length];
            Beziers[0] = new BezierCubic2D(source[0], source[1], source[2], source[3]);
            for (int i = 1; i < length; i++)
                Beziers[i] = new BezierCubic2D(source[(i * 3)], source[(i * 3) + 1], source[(i * 3) + 2], source[(i * 3) + 3]);
            _ArcLength = 0.0F;
            _ArcLengths = new List<float>();
        }

        public bool FindTAtPointOnSpline(float distanceAlongSpline, out BezierCubic2D bezier, out float t)
        {
            // to do: cache last distance and bezier. if new distance > old then start from old bezier.
            if (distanceAlongSpline > ArcLength) { bezier = Beziers[Beziers.Length - 1]; t = distanceAlongSpline / ArcLength; return false; }
            if (distanceAlongSpline <= 0.0F)
            {
                bezier = Beziers[0];
                t = 0.0F;
                return true;
            }
            for (int i = 0; i < Beziers.Length; i++)
            {
                float distanceRemainingBeyondCurrentBezier = distanceAlongSpline - Beziers[i].ArcLength;
                if (distanceRemainingBeyondCurrentBezier < 0.0F)
                {
                    // t is in current bezier.
                    bezier = Beziers[i];
                    t = bezier.FindTAtPointOnBezier(distanceAlongSpline);
                    return true;
                }
                else if (distanceRemainingBeyondCurrentBezier == 0.0F)
                {
                    // t is 1.0F. Bezier is current one.
                    bezier = Beziers[i];
                    t = 1.0F;
                    return true;
                }
                // reduce the distance by the length of the bezier.
                distanceAlongSpline -= Beziers[i].ArcLength;
            }
            // point is outside the spline.
            bezier = new BezierCubic2D();
            t = 0.0F;
            return false;
        }
        public void BendShapeToSpline(System.Drawing.RectangleF bounds, int dataLength, ref System.Drawing.PointF[] data, ref byte[] dataTypes)
        {
            System.Drawing.PointF pt;
            // move the origin for the data to 0,0
            float left = bounds.Left, height = bounds.Y + bounds.Height;

            for (int i = 0; i < dataLength; i++)
            {
                pt = data[i];
                float textX = pt.X - left;
                float textY = pt.Y - height;

                if (FindTAtPointOnSpline(textX, out BezierCubic2D bezier, out float t))
                {
                    data[i] = bezier.PointOnCurvePerpendicular(t, true, textY).ToPointF();
                }
                else
                {
                    // roll back all points until we reach curvedTypes[i] == 0
                    for (int j = i - 1; j > -1; j--)
                    {
                        if ((dataTypes[j] & 0x80) == 0x80)
                        {
                            System.Drawing.PointF[] temp1 = new System.Drawing.PointF[j + 1];
                            Array.Copy(data, 0, temp1, 0, j + 1);
                            byte[] temp2 = new byte[j + 1];
                            Array.Copy(dataTypes, 0, temp2, 0, j + 1);
                            data = temp1;
                            dataTypes = temp2;
                            break;
                        }
                    }
                    break;
                }
            }
        }

        private void CalculateArcLength()
        {
            _ArcLength = 0.0F;
            _ArcLengths = new List<float>(Beziers.Length);
            for (int i = 0; i < Beziers.Length; i++)
            {
                _ArcLength += Beziers[i].ArcLength;
                _ArcLengths.Add(_ArcLength);
            }
        }

        internal static System.Drawing.PointF[] GetCurveTangents(System.Drawing.Point[] points, int count, float tension, int curveType)
        {
            if (points == null)
                throw new ArgumentNullException("points");

            System.Drawing.PointF[] pointfs = new System.Drawing.PointF[count];
            for (int p = 0; p < count; p++)
            {
                pointfs[p] = new System.Drawing.PointF(points[p].X, points[p].Y);
            }

            return GetCurveTangents(pointfs, count, tension, curveType);
        }
        internal static System.Drawing.PointF[] GetCurveTangents(System.Drawing.PointF[] points, int count, float tension, int curveType)
        {
            float coefficient = tension / 3f;
            System.Drawing.PointF[] tangents = new System.Drawing.PointF[count];

            if (count < 2)
                return tangents;

            for (int i = 0; i < count; i++)
            {
                int r = i + 1;
                int s = i - 1;

                if (r >= count)
                    r = count - 1;
                if (curveType == 0) // 0 == CurveType.Open
                {
                    if (s < 0)
                        s = 0;
                }
                else // 1 == CurveType.Closed, end point jumps to start point
                {
                    if (s < 0)
                        s += count;
                }

                tangents[i].X += (coefficient * (points[r].X - points[s].X));
                tangents[i].Y += (coefficient * (points[r].Y - points[s].Y));
            }

            return tangents;
        }
        internal static System.Drawing.PointF[] CreateCurve(System.Drawing.Point[] points, int offset, int length, int curveType)
        {
            if (points == null)
                throw new ArgumentNullException("points");

            System.Drawing.PointF[] pointfs = new System.Drawing.PointF[length];
            for (int p = 0; p < length; p++)
            {
                pointfs[p] = new System.Drawing.PointF(points[p].X, points[p].Y);
            }

            System.Drawing.PointF[] tangents = GetCurveTangents(pointfs, length, 0.5F, 0);
            return CreateCurve(pointfs, tangents, offset, length, curveType);
        }
        internal static System.Drawing.PointF[] CreateCurve(System.Drawing.Point[] points, System.Drawing.PointF[] tangents, int offset, int length, int curveType)
        {
            if (points == null)
                throw new ArgumentNullException("points");

            System.Drawing.PointF[] pointfs = new System.Drawing.PointF[length];
            for (int p = 0; p < length; p++)
            {
                pointfs[p] = new System.Drawing.PointF(points[p].X, points[p].Y);
            }

            return CreateCurve(pointfs, tangents, offset, length, curveType);
        }
        internal static System.Drawing.PointF[] CreateCurve(System.Drawing.PointF[] points, System.Drawing.PointF[] tangents, int offset, int length, int curveType)
        {
            List<System.Drawing.PointF> curve = new List<System.Drawing.PointF>();
            int i;

            Append(curve, points[offset].X, points[offset].Y, true);
            for (i = offset; i < offset + length - 1; i++)
            {
                int j = i + 1;

                float x1 = points[i].X + tangents[i].X;
                float y1 = points[i].Y + tangents[i].Y;

                float x2 = points[j].X - tangents[j].X;
                float y2 = points[j].Y - tangents[j].Y;

                float x3 = points[j].X;
                float y3 = points[j].Y;

                AppendBezier(curve, x1, y1, x2, y2, x3, y3, false);
            }
            return curve.ToArray<System.Drawing.PointF>();
        }
        internal static void Append(List<System.Drawing.PointF> points, float x, float y, bool compress)
        {
            System.Drawing.PointF pt = System.Drawing.PointF.Empty;

            /* in some case we're allowed to compress identical points */
            if (compress && (points.Count > 0))
            {
                /* points (X, Y) must be identical */
                System.Drawing.PointF lastPoint = points[points.Count - 1];
                if ((lastPoint.X == x) && (lastPoint.Y == y))
                {
                    return;
                }
            }

            pt.X = x;
            pt.Y = y;

            points.Add(pt);
        }
        internal static void AppendBezier(List<System.Drawing.PointF> points, float x1, float y1, float x2, float y2, float x3, float y3, bool isReverseWindingOnFill)
        {
            if (isReverseWindingOnFill)
            {
                Append(points, y1, x1, false);
                Append(points, y2, x2, false);
                Append(points, y3, x3, false);
            }
            else
            {
                Append(points, x1, y1, false);
                Append(points, x2, y2, false);
                Append(points, x3, y3, false);
            }
        }

    }
}
}

View of text bent around 14 points


1

我认为唯一的方法是逐个渲染每个字符并使用

Graphics.RotateTransform

旋转文本。您需要自己计算旋转角度和渲染偏移量。您可以使用

Graphics.MeasureCharacterRanges

获取每个字符的大小。


0

不幸的是,在GDI+中没有办法将字符串附加到路径上(这就是你要寻找的内容)。

因此,唯一的方法是手动完成。这意味着将字符串拆分为字符,并根据自己的路径计算放置它们。

除非你想花费大量的工作来完成这个任务,否则你应该尝试找到一个库(潜在的完整GDI+替代品)来完成这个任务,或者放弃你的彩虹。

使用WPF,您可以在路径上呈现文本(请参阅链接以获取如何操作)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接