如何将弯曲文本渲染为位图?

发布于 2024-08-31 19:27:33 字数 270 浏览 7 评论 0原文

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

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

这将返回一个矩形位图,其中字符串从左到右直接书写。 我也希望能够把绳子画成彩虹的形状。 我该怎么做?

I am currently dynamically creating a bitmap and using the graphics object from the bitmap to draw a string on it like so:

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

This returns a rectangular bitmap with the string written straight across from left to right.
I would like to also be able to draw the string in the shape of a rainbow.
How can I do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

懒猫 2024-09-07 19:27:33

我最近遇到了这个问题(我正在渲染文本以​​打印到 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);
}

I recently had this problem (I was rendering text for printing onto CDs), so here's my solution:

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

凡间太子 2024-09-07 19:27:33

我需要用原始 C# 来回答这个问题,但找不到任何可以这样做的示例:

这个解决方案需要大量的数学知识来解决。总之,它需要一组点(2D 向量),将它们与基线对齐,然后围绕样条曲线弯曲这些点。该代码足够快,可以实时更新并处理循环等。

为了方便起见,该解决方案使用 GraphicsPath.AddString 将文本转换为矢量,并使用 GraphicsPath.PathPoints/PathTypes 作为点,但是您可以使用相同的功能。 (不过我不建议实时制作 4k 位图)。

该代码有一个简单的 Paint 函数,后面是 Spline 类。 GraphicsPath 在 Paint 方法中使用是为了使代码更容易理解。 ClickedPoints 是您希望文本弯曲的点的数组。我使用了一个列表,因为它被添加到鼠标事件中,如果您事先知道这些点,请使用数组。

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 returned around 14 points

I needed to answer this question in raw C# and could not find any samples that do it so:

This solution requires a lot of Maths to solve. In summary it takes a set of points (2D vectors), aligns them to a baseline and then bends the points around a Spline. The code is fast enough to update in real time and handles loops, etc.

For ease the solution turns text into vectors using GraphicsPath.AddString and uses GraphicsPath.PathPoints/PathTypes for the points, however you can bend any shape or even bitmaps using the same functions. (I would not recommend doing 4k bitmaps in real time though).

The code has a simple Paint function followed by the Spline class. GraphicsPath is used in the Paint method to make the code hopefully easier to understand. ClickedPoints are the array of points you want the text bent around. I used a List as it was added to in Mouse events, use an array if you know the points beforehand.

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);
}

And now for the spline class:

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

罪歌 2024-09-07 19:27:33

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

Graphics.RotateTransform

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

Graphics.MeasureCharacterRanges

来获取每个字符的大小。

I think the only way is to render each character individually and use the

Graphics.RotateTransform

to rotate the text. You'll need to work out the rotation angle and rendering offset yourself. You can use the

Graphics.MeasureCharacterRanges

to get the size of each character.

秋心╮凉 2024-09-07 19:27:33

不幸的是,在 GDI+ 中,无法将字符串附加到路径(这正是您所需要的)。

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

除非您想为此投入大量工作,否则您应该尝试找到一个库(可能完全替代 GDI+)来完成此操作,否则就放弃您的彩虹。

使用 WPF,您可以在路径上呈现文本(查看操作方法链接)

Unfortunatelly in GDI+ there is no way to attach Strings to a path (this is what you would be looking for).

So the only way to do this is doing it "by hand". That means splitting up the string into characters and placing them based on your own path calculations.

Unless you want to put a lot of work into this you should try to find a library (potentially complete GDI+ replacement) to do this or give up on your rainbow.

With WPF you can render text on a path (see link for a howto)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文