Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Text To Image With Rotation For The Web

0.00/5 (No votes)
31 Mar 2010 1  
This article will explain how to create an HttpHandler that will accept a text string, optionally rotate it, and return an image of the rotated text.

Download TextImage.zip - 2.53 KB

Introduction

This article will explain how to create an HttpHandler that will accept a text string, optionally rotate it, and return an image of the rotated text. 

Background

I recently had the desire to rotate some text on a web page.  I would have thought this would be easily accomplished using CSS but unfortunately that is not the case.  While there are some CSS properties that allow text rotation, they vary from browser to browser and all I could find were limited to only allowing 90 degree increments.  I wanted to be able to rotate text to any degree.

It wasn't long before I discovered that the only approach that seemed like it would work was to dynamically generate an image of the text after rotating it.

Development Environment

I don't believe there are any features of C# used in this code that require C# 3.0 to be used.  I also believe that the only C# 2.0 feature I am using is the coalesce operator.  I use it for initializing some variables and this part could easily be rewritten to not use the coalesce operator if you really needed it to work with C# 1.0.  However, I developed this code using Visual Studio 2008 and that may make creating the HttpHandler easier than older versions of Visual Studio.

Challenges

There were several challenges I would have to overcome for me to write this code.

HttpHandler

First, I realized that implementing this as an HttpHandler was the way to go.  This would allow me to embed the URL to the HttpHandler in any image's src attribute in my website.  In fact, if I made the HttpHandler more globally available, I could embed it in any web page that could reach the URL regardless of the web page's nature.  By this I mean that the page wouldn't need to be generated by ASP.NET.  It could be a JSP page hosted on a Java-based web server or it could even be a static HTML page on any web server.  I have never written an HttpHandler before but it turned out to be quite simple using Visual Studio 2008 and there are many examples of this on the web.

Programmatically Generating an Image

Additionally, I would need to programmatically generate an image of the text.  I have never done this before, but it too turned out to be fairly trivial.  There are many examples of this on the web.

Streaming an Image

I knew I didn't want to actually create a physical file for the image so streaming the image to the browser was the way to go.  I knew this could be done because I use a barcode library that does the same thing.  It too is implemented as an HttpHandler and there are many examples on the web of streaming an image to the browser.

Image Transparency

For my purpose of this code, I needed to be able to overlay multiple text images and for them to not block each other.  This meant I would need the ability to create the image with a transparent background.  Transparency immediately limits us to creating GIF and PNG files and there are issues with both.  Older browsers, specifically Internet Explorer 6.0 and older, don't support PNG transparency properly.  The .NET library for programmatically creating GIF images doesn't work well with transparency either.  This presents problems either way.

I was able to find a few articles on the web that could hack the GIF transparency problem but most were very complicated and required a lot of code.  I finally found an article that did it with very little code and that is the approach I took.  There are also supposed hacks for the Internet Explorer PNG transparency problem available on the web.  I tried one several years ago but didn't get it to work.  I assume that was my fault though because there are enough articles about the hack that it must work.  I mention this in case you want to generate PNG images and want to know a solution for older browsers.

This issue turned out to be quite challenging to overcome, but our HttpHandler will be able to create either a GIF or PNG file depending on the query string arguments passed to it.

Text Rotation

The very basis for this code was the need to rotate text and this seems like it should be trivial.  There is a simple .NET API to call to rotate the text and there are many examples on the web.  It is true that it is easy to rotate text.  What is difficult though is actually getting to see the text once it is rotated.  This is due to the rotation point when rotating something with the .NET API.  It turns out that the rotation point is always the top left corner.  This is very unintuitive in my opinion and makes it very difficult to rotate things with the API.  The problem is that if your rotate around the top left corner, what you are rotating completely leaves the image once it enters the third geometric quadrant and stays completely hidden in the second quadrant and for much of the first quadrant, and part of the fourth.

TextRotatedInQuadrants.jpg

In the diagram above, the actual text image is represented by the black box around the "Text @0" text.  As you can see, the text has completely left the image at 90 degrees of rotation and beyond.  In truth, it begins leaving the image after only a few degrees of rotation but at these early stages you can still see most of the text so you can tell it is there.  But once you hit 90 degrees, the text is completely gone.  This is quite a mystery until you know what is happening.

This is apparently a common problem because it isn't difficult to find questions on the web like, "I just rotated my thing-a-ma-jig but it disappeared.  What am I doing wrong?"  Sadly, this turned out to be the most difficult challenge to overcome and required me to reinvest some time with trigonometry!

Trigonometry

It has been 20-some years since I did trigonometry in high school and college.  Who would have ever thought at this late stage of my career it would actually become practical and useful?  Certainly not I!

So my text was leaving the image but I found that there was an API that would allow me to move the rotation point.  I thought I was on to something.  I just wanted to move the rotation point to the center of the image and have the text rotate around that point.  It seemed simple enough.  Nope.  No dice.  Remember, earlier I said that the rotation always occurs around the top left corner.  So I could move the rotation point to the center of the image, but the text still rotated around that point as its top left and the results were even worse.  The text left the image even more dramatically!

I was having a lot of difficulty nailing down the solution for this.  I tried guessing numbers until I could see the text and then tried to determine the pattern, the mathematical equation, to predict the needed numbers.  It didn't seem very systematic...at least not to my linearly thinking mind.

The Flash of Genius

In the movie Flash of Genius, the character played by actor Greg Kinnear tells his story to the jury.  He cites a previous case where the court stated that an invention was often conceived in a flash of genius and Kinnear continues by explaining his flash of genius leading to his invention of the intermittent windshield wiper.  My flash of genius for solving this problem was when I realized that for the fourth quadrant (rotation degrees 0 through 89), I wanted to simply slide the rotation point along the x-axis, keeping the rotation point on y = 0.  In this discussion I will refer to the text height as th and the text width as tw.  Both of these can be determined using a .NET API.

SlideXInIV.jpg

In the diagram above I am simply sliding the top left corner of the image along the x-axis maintaining y = 0.  This keeps the text in the image the entire time.  This also makes it pretty clear that the x coordinate ranges from 0 to the height of the text, th.  Now I simply needed to determine the trigonometry to determine that x coordinate.

QuadIV.jpg

In the diagram above, I can see that the angle of rotation, which is 30 degrees, is also the angle of rotation of the inner triangle from the y-axis.  I already know my text height, th, using a .NET API.  Since trigonometry states that SIN θ = Opposite/Hypotenuse, I can determine X.  Using algebra, I now know that X = SIN 30 * th.  I have to convert the 30 degrees to radians first though for the .NET math APIs.  So, I now have my equation for the fourth quadrant!

Math.Sin(radians) * th

I then discovered that for the first quadrant (rotation degrees 271 through 359), I simply needed to slide down along my y-axis while keeping x = 0.  Using a diagram similar to the one above, I realized that y would vary from 0 to the width of the text, tw, which I also knew from a .NET API.  Again, a simple trigonometric formula provided my y coordinate:

Math.Sin(radians) * tw

I would have thought that determining the formula for the second and third quadrants would be trivial and merely mirror images of the first and fourth quadrants, but I received no such luck.  For these two quadrants, my x and y coordinates would both have to move.  By now though, my trigonometric juices were flowing!  It was like I was back in 11th grade.  Actually, while I was more trig-savvy back then, I am not sure I could have applied the math as well as now.

QuadIII.jpg

Examining the diagram above, you can see things are starting to get more complex.  In the third quadrant, the top left corner of the text will follow a curved path from x = th, y = 0 to x = tw, y = th.    To determine the formula, a closer look is needed.

QuadIIIZoom.jpg

In the diagram above I have rotated the text 120 degrees.  Now would be a good time to mention that for some quadrants, I calculate a reference angle from the actual rotation angle.  For the third quadrant, I subtract 90 from the rotation angle.  So for a 120 degree rotation, my reference angle becomes 30 and that is the angle you see marked as θ in the diagram above.  I know that my x coordinate needs to be x1 + x2.  For this calculation, I will also need to know the definition for COS.  If you don't recall your trigonometry for COS, COS θ = Adjacent/Hypotenuse.  Therefore:

x1 = Math.Sin(radians) * tw

x2 = Math.Cos(radians) * th

So my new x coordinate is:

(Math.Sin(radians) * tw) + (Math.Cos(radians) * th)

My y coordinate is:

Math.Sin(radians) * th

Determining the new x and y coordinate for the second quadrant is similar to the third except I end up with needing to determine y1 and y2 and a single x.

Image Resizing

As you have examined these diagrams you may have noticed that the image size needs to change too.  When the text is rotated 90 degrees, the width of the image needs to be set to the height of the text and the height of the image needs to be set to the width of the text.  In between, there is more trig, but not nearly as complex as determining the new coordinates.  In fact, when I was developing the code, I actually wrote this part first and found it not too difficult.  So even after using trigonometry to determine the new image size after rotation, it was still a challenge determining the new coordinates.  Again, were it not for the "flash of genius", I am not sure where this would have gone.

Analyzing the Code

Let's get started with the code.  The first step will be to add an HttpHandler to either a new project or an existing one.  In some ways, I really love the idea of creating a standalone ASP.NET website to host this HttpHandler because don't forget, once this is running, any html page that can hit your website hosting the handler could call it and convert text to an image.

Creating the HttpHandler

For this article I will assume you already have an ASP.NET website or web application and simply want to add an HttpHandler to it.  In Visual Studio 2008, right click on the project in the Solution Explorer and select the Add New Item menu and select the Generic Handler template.  Edit the Name field to TextImage.ashx.  This will create a file for you named TextImage.ashx with a stubbed in class named Handler that implements the IHttpHandler interface.  It will also generate a ProcessRequest method and IsReusable read-only property for you since that is what the IHttpHandler interface requires.

The Using Directives

For this code, you will need the following using directives:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.IO;
using System.Web;

Private Static Variables

Because I will use default values in more than one place for the same parameter, rather than providing literal values for the default everywhere needed, I wanted to create some private static variables for each parameter's default value and reuse these throughout the code to ensure consistency of the default values.

public class TextImage : IHttpHandler {
    private static string d_fontFamily = "Arial";
    private static int d_fontSize = 10;
    private static bool d_bold = false;
    private static bool d_italic = false;
    private static string d_color = "Black";
    private static string d_bgColor = "Transparent";
    private static int d_rotation = 0;
    private static int d_underlineWidth = 0;
    private static string d_underlineColor = "Black";
    private static bool d_usePng = false;

There isn't much going on with this code so far.  I have just defined some private static variables and assigned default values for each parameter.  This is to prevent requiring every parameter to be passed.

ProcessRequest

Let's take a look at the code to implement the ProcessRequest method.  It won't do any of the real work.  It will merely gather the passed parameters and handle default values for them as well as return a template (in an image since that is what the caller is expecting to be returned) when appropriate.

    public void ProcessRequest (HttpContext context) {
        string fontFamily = context.Request.QueryString["FontFamily"] ?? d_fontFamily;
        int fontSize = Int32.Parse(context.Request.QueryString["FontSize"] ?? d_fontSize.ToString());
        bool bold = Boolean.Parse(context.Request.QueryString["Bold"] ?? d_bold.ToString());
        bool italic = Boolean.Parse(context.Request.QueryString["Italic"] ?? d_italic.ToString());
        string color = context.Request.QueryString["TextColor"] ?? d_color;
        string bgColor = context.Request.QueryString["BackgroundColor"] ?? d_bgColor;
        int rotation = Int32.Parse(context.Request.QueryString["Rotation"] ?? d_rotation.ToString());
        int underlineWidth = Int32.Parse(context.Request.QueryString["UnderlineWidth"] ?? d_underlineWidth.ToString());
        string underlineColor = context.Request.QueryString["UnderlineColor"] ?? d_underlineColor;
        bool usePng = Boolean.Parse(context.Request.QueryString["UsePng"] ?? d_usePng.ToString());

So far all I have done is create some local variables for the parameters and intialized them to either the passed parameter or the default value.  Here I am using the C# 2.0 coalesce operator (??) so if you require C# 1.0, you could simply rewrite this initialization section.

        string text = "";
        if (context.Request.QueryString["Text"] == null)
        {
            //  Since no query string parameters have been passed, I assume the user
            //  doesn't know how to call this handler, so let's output the parameters.
            text = "Supported parameters: Text,FontFamily,FontSize,Bold,Italic,TextColor,BackgroundColor,Rotation,UnderlineWidth,UnderlineColor,UsePng\nExample: TextImage.ashx?Text=LINQ%20Rocks!&FontFamily=Arial&FontSize=18&Bold=True&Italic=False&TextColor=Black&BackgroundColor=Transparent&Rotation=90&UnderlineWidth=0&UnderlineColor=%23FF0000&UsePng=False";
            fontFamily = d_fontFamily;
            fontSize = d_fontSize;
            bold = d_bold;
            italic = d_italic;
            color = d_color;
            bgColor = d_bgColor;
            rotation = d_rotation;
            underlineWidth = d_underlineWidth;
            underlineColor = d_underlineColor;
            usePng = d_usePng;
        }
        else
        {
            text = context.Request.QueryString["Text"];
        }

In the code above, I check the Text parameter and if it wasn't passed, I determine this to be a request for the template help and set all the parameters to their default values.  So even if some of the values were passed, I have now reverted them back to their default values.  This is so that parameters that might make it difficult to read the parameter help won't interfere.  For example, if the font size was passed as 4, or the rotation was passed as 180, I have reverted them to something more readable like font size 10 and rotation 0.

        Byte[] buffer = 
            CreateTextImage(text, fontFamily, fontSize, bold, italic, color, bgColor, rotation, underlineWidth, underlineColor, usePng);

        context.Response.ContentType = usePng ? "image/png" : "image/gif";
        context.Response.BinaryWrite(buffer);
        context.Response.Flush(); 
    }

In the code above, I just call the CreateTextImage method that does all the work.  It will return an array of bytes that I then write to the response.  Notice that I set the ContentType based on the passed UsePng parameter, which defaults to false.  So by default, a GIF file will be generated, but if you pass UsePng=True, the ContentType will be set to png.  Last, I flush the buffer.

Now let's look at the CreateTextImage method where all the work occurs.

    private byte[] CreateTextImage(string text, string fontFamily, int fontSize, bool bold, bool italic, string color, string bgColor, int rotation, int underlineWidth, string underlineColor, bool usePng)
    {
        float textWidth = 0;
        float textHeight = 0;

        FontStyle fontStyle = FontStyle.Regular;
        if (bold)
            fontStyle |= FontStyle.Bold;
        if (italic)
            fontStyle |= FontStyle.Italic;

First I create some textWidth and textHeight variables and initialize them to 0.  I then create a FontStyle object and set it based on the bold and italic setting that was passed to the handler through the query string. 

        using (Font font = new Font(fontFamily, fontSize, fontStyle))
        {
            //  Get the text measurements.
            //  I need to create a dummy bitmap so that I can get a Graphics object to get the text measurements.
            using (Bitmap bitmap = new Bitmap(1, 1))
            {
                using (Graphics graphics = Graphics.FromImage(bitmap))
                {
                    textWidth = graphics.MeasureString(text, font).Width;
                    textHeight = graphics.MeasureString(text, font).Height;
                }
            }

Next I create a Font using the font family, font size, and font styles that were passed.  I then create a dummy bitmap to obtain a Graphics object from, which I then use to determine the text height and width using the MeasureString method.  This is the .NET API I was referring to in the trigonometry section for determining the height and width of the text.

            //  Calculate the needed bitmap measurements based on the text measurements.
            int bitmapWidth = GetRotatedRectangleWidth(textWidth, textHeight, rotation);
            int bitmapHeight = GetRotatedRectangleHeight(textWidth, textHeight, rotation);

I then use a couple of local methods I wrote to determine the new image's size after the rotation.  These methods are using trig but aren't nearly as complex as the ones determining the rotation point's new location.

            //  Now I create the real bitmap of the necessary size to fit the text.
            using (Bitmap bitmap = new Bitmap(bitmapWidth, bitmapHeight))
            {
                using (Graphics graphics = Graphics.FromImage(bitmap))
                {
                    if (!usePng)
                    {
                        graphics.Clear(Color.FromArgb(255, 255, 255, 204));
                    }

Next I create a new Bitmap object, this time the real one, using the calculated width and height.  I obtain a Graphics object from it, and then if a GIF image has been requested, I do part of the GIF transparency hack.

                    //  Since I will be rotating text, I need to move the rotation point using the TranslateTransform.
                    int x, y;
                    //  But first I need to know the location of the rotation point.
                    GetXY(rotation, textWidth, textHeight, out x, out y);
                    graphics.TranslateTransform(x, y);

Next I call the GetXY method to obtain the new rotation point.  This is where all the complex trig happens.  Then I call the TranslateTransform method to move the rotation point.

                    //  Now rotate and draw the text.
                    graphics.RotateTransform(rotation);

The RotateTransform method is the .NET API that actually rotates the image.

                    //  Fill in the background color
                    using (Brush brush = new SolidBrush(ColorTranslator.FromHtml(bgColor)))
                    {
                        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                        graphics.FillRectangle(brush, 0, 0, textWidth, textHeight);
                    }
 
                    graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
                    using (Brush brush = new SolidBrush(ColorTranslator.FromHtml(color)))
                    {
                        graphics.DrawString(text, font, brush, 0, 0);
                    }

I fill in the background color and then write the text using the DrawString method using the font that was created earlier.

                    if (underlineWidth > 0)
                    {
                        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                        using (Pen pen = new Pen(ColorTranslator.FromHtml(underlineColor), underlineWidth))
                        {
                            graphics.DrawLine(pen, 0, textHeight, textWidth, textHeight);
                        }
                    }


                    graphics.Flush();

Then, if an underline was requested, I draw it and flush the Graphics object.

                    MemoryStream m = new MemoryStream();
                    bitmap.Save(m, usePng ? ImageFormat.Png : ImageFormat.Gif);

Next I create a MemoryStream and save the newly generated Bitmap object to it.

                    if (usePng)
                    {
                        return m.ToArray();
                    }
                    else
                    {
                        // transparency hack.
                        byte[] n = { };
                        n = m.ToArray();
                        n[787] = 254;
                        return n;
                    }

If we are generating a PNG file, I can simply return the array of bytes in the MemoryStream object.  However, if a GIF was requested, it is time for the GIF transparency hack.

                } // using graphics
            } // using bitmap
        } // using font
    }

This completes the main code.  Now all that is left are some worker methods to handle the math.

    private void GetXY(int rotation, float tw, float th, out int xT, out int yT)
    {
        xT = 0;
        yT = 0;

        double radians = GetRadians(GetReferenceAngleForPositioning(rotation));

        if (rotation >= 0 && rotation <= 90)
        {
            xT = Convert.ToInt32(Math.Sin(radians) * th);
        }
        else if (rotation > 90 && rotation <= 180)
        {
            xT = Convert.ToInt32((Math.Sin(radians) * tw) + (Math.Cos(radians) * th));
            yT = Convert.ToInt32(Math.Sin(radians) * th);
        }
        else if (rotation > 180 && rotation <= 270)
        {
            xT = Convert.ToInt32(Math.Cos(radians) * tw);
            yT = Convert.ToInt32((Math.Cos(radians) * th) + (Math.Sin(radians) * tw));
        }
        else
        {
            yT = Convert.ToInt32(Math.Sin(radians) * tw);
        }
    }

The GetXY method above is in my opinion the real jewel in this code.  This is where the trigonometry I discussed earlier gets performed.  Since I already discussed most of the logic here in the Trigonometry section, I'll move on. 

    private int GetRotatedRectangleWidth(float width, float height, int rotation)
    {
        return GetRotatedRectangleWidth(width, height, GetRadians(GetReferenceAngleForSizing(rotation)));
    }
 
    private int GetRotatedRectangleWidth(float width, float height, double rotationInRadians)
    {
        double w1 = width * Math.Cos(rotationInRadians);
        double w2 = height * Math.Sin(rotationInRadians);
 
        return Convert.ToInt32(Math.Ceiling(w1 + w2));
    }

Above are a couple methods to determine the rotated image width.  After all the trig so far, these are fairly simple.

    private int GetRotatedRectangleHeight(float width, float height, int rotation)
    {
        return GetRotatedRectangleHeight(width, height, GetRadians(GetReferenceAngleForSizing(rotation)));
    }
 
    private int GetRotatedRectangleHeight(float width, float height, double rotationInRadians)
    {
        double h1 = width * Math.Sin(rotationInRadians);
        double h2 = height * Math.Cos(rotationInRadians);
 
        return Convert.ToInt32(Math.Ceiling(h1 + h2));
    }

Above are a couple methods to determine the rotated image height.

    private double GetRadians(double referenceAngle)
    {
        return Math.PI * referenceAngle / 180.0;
    }

The GetRadians method just converts the rotation degrees to radians which are needed for the .NET math APIs.

    //  The only difference between the reference angles for sizing and positioning is in the 3rd quadrant (> 90 <= 180).
    private double GetReferenceAngleForSizing(int rotationInDegrees)
    {
        if (rotationInDegrees >= 0 && rotationInDegrees <= 90)
            return rotationInDegrees;
        else if (rotationInDegrees > 90 && rotationInDegrees <= 180)
            return 180 - rotationInDegrees;
        else if (rotationInDegrees > 180 && rotationInDegrees <= 270)
            return rotationInDegrees - 180;
        else if (rotationInDegrees > 270 && rotationInDegrees <= 360)
            return 360 - rotationInDegrees;
        else
            return rotationInDegrees;
    }
 
    private double GetReferenceAngleForPositioning(int rotationInDegrees)
    {
        if (rotationInDegrees >= 0 && rotationInDegrees <= 90)
            return rotationInDegrees;
        else if (rotationInDegrees > 90 && rotationInDegrees <= 180)
            return rotationInDegrees - 90;
        else if (rotationInDegrees > 180 && rotationInDegrees <= 270)
            return rotationInDegrees - 180;
        else if (rotationInDegrees > 270 && rotationInDegrees <= 360)
            return 360 - rotationInDegrees;
        else
            return rotationInDegrees;
    }

The two methods above simply determine reference angles for determining the new rotated image size and rotation point positioning, respectively.  Someone stronger at geometry and trigonometry could probably reduce this to a single method.

I have one warning here though about doing that.  In some of my earlier testing, before I arrived at the current calculations for determing the new rotation point, I had a different calculation that was using the TAN trigonometric function.  Be careful because TAN blows up (approaches infinity and negative infinity) at boundaries.  I had to write special handling as the rotation approached 90 degree intervals.  It was later that I realized that SIN and COS are much better behaved for this.  You can see this behavior of TAN at wikipedia by looking up trigonometry.  There you can see some animated graphs of SIN, TAN, and CSC.  Notice that the graph for TAN goes off the chart toward infinity and reappears from negative infinity on its way back to positive infinity.  Contrast that with the animation for SIN which stays within a well-defined range of -1 to +1.

Using the Code

Using the HttpHandler is the best part of this approach because it can be embedded in any HTML img tag's src attribute like this:

<img src="http://localhost/TextToImageCS/TextImage.ashx?Text=joe@rattz.com&FontFamily=Calibri&FontSize=12&TextColor=Black&BackgroundColor=Transparent&Rotation=0&UnderlineWidth=0&UnderlineColor=Black&UsePng=False" />

Here is the image created by that call:

joeatrattz.com.jpg

I'll get a little fancy with the next one by changing some of the parameters such as the rotation, underline, font family, font size, and color.  Here is the img tag:

<img src="http://localhost/TextToImageCS/TextImage.ashx?Text=LINQ%20Rulez!&FontFamily=Arial&FontSize=18&TextColor=Blue&BackgroundColor=Transparent&Rotation=270&UnderlineWidth=2&UnderlineColor=Black&UsePng=False" />

Please notice that I used %20 for the space between the words LINQ and Rulez so that the URL is properly encoded.  Here is the image that is generated:

LINQRulez_.jpg

One last example with all the fancy options and 135 degrees of rotation:

<img src="http://localhost/TextToImageCS/TextImage.ashx?Text=Code%20Project%20Rocks!&FontFamily=TimesNewRoman&FontSize=32&TextColor=Red&BackgroundColor=Blue&Rotation=135&UnderlineWidth=4&UnderlineColor=Red&UsePng=False" />

And here is the image it produces:

Code_Project_Rocks_.jpg

Of course you will need to change the URLs to point to your HttpHandler.

The full module is attached at the top of this article as TextImage.zip.

Points of Interest

Well obviously, after writing this code I have a new-found appreciation for trigonometry, which at this late stage of my career I would have never expected.  I learned a lot from each of the challenges.  I hope you find something here beneficial.

Uses

At this point, you may be wondering, what is the point?  For my purpose, I wanted rotated text so that I could have a table with column headers at an angle like the image below demonstrates.

ThePoint.jpg

In that image, the horizontal stuff is just a generated HTML table, but the column headings are rotated images.  And, now you can see my purpose for the underline feature!  Test code generated the HTML above, but it demonstrates the purpose.

This code could also be used to create captcha style images of text.  One person that wrote some code to convert text to images did so to prevent email address harvesting by web bots.  Instead of embedding textual email addresses in a page, he created a text image.

With some modification, this code could be used to dynamically add captions or watermarked text to existing images.

Credits

Since this code required overcoming so many challenges that I have never done before, I visited a lot of websites and read a lot of articles.  If you have written any articles about any of this stuff, I very well may have read yours.  Unfortunately I visited so many, I wouldn't know who to thank for any specific piece except one, Salman Arshad.  I got the GIF transparency hack from his blog at http://911-need-code-help.blogspot.com.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here