Thursday, October 1, 2009

ColdFusion 8: Draw a Text Outline on an Image

In one of my older entries, I wrote about how to fill text with an image. A recent poster asked about doing something similar, except they wanted to draw an outline of the text instead of filling it with an image. As far as I know, this is not supported using the basic ColdFusion image functions. But there is a great example of how to do this in one of the older java2D tutorials. While there is not much to the example, it does use a few interesting concepts. So I decided to write up a quick CF translation.

All you need is a new image that supports transparency. Next, grab the underlying graphics object from the image. You will be using it to draw the text outline onto the image.


<cfscript>
transparentImage = ImageNew("", width, height, "argb");
graphics = ImageGetBufferedImage(transparentImage).createGraphics();
</cfscript>


Now here is where you veer into the java code. The first step in the example is to enable antialiasing for smoother text. This is achieved with RenderingHints. If you are not familiar with them, think of them as java's version of ImageSetAntialiasing().


<cfscript>
RenderingHints = createObject("java", "java.awt.RenderingHints");
graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
graphics.setRenderingHint( RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY
);
</cfscript>


Next the example creates a graphical representation of the text. In other words, it gets the shape of the rendered text. This is done with a neat class called TextLayout. TextLayout handles all the hard work of positioning, spacing, text styles and generating the final glyphs.


<cfscript>
Font = createObject("java", "java.awt.Font");
textFont = Font.init("Arial", Font.PLAIN, width / 5 );
textColor = createObject("java", "java.awt.Color").decode("##80aa1c");
fontContext = graphics.getFontRenderContext();
layout = createObject("java", "java.awt.font.TextLayout").init(text, textFont, fontContext);
</cfscript>


But before the text shape can be drawn onto the image, it must be positioned. This is done with an AffineTransform. In this case it is a fancy way of saying move the x,y drawing coordinates. (I tweaked the code example to center the text).


<cfscript>
transX = (width/2) - (layout.getBounds().getWidth()/2);
transY = (height/2) + layout.getDescent();
transform = createObject("java", "java.awt.geom.AffineTransform").init();
transform.setToTranslation( transX, transY );
</cfscript>


Finally, the example grabs the shape of the text, sets the text color and draws the outline onto the image. That is all there is to it.


<cfscript>
shape = layout.getOutline(transform);
graphics.setColor(textColor);
graphics.draw(shape);
graphics.dispose();
</cfscript>




Full Code

<cfscript>
width = 330;
height = 150;
text = "Crossfade";

// Create a new image that supports transparency
transparentImage = ImageNew("", width, height, "argb");
graphics = ImageGetBufferedImage(transparentImage).createGraphics();

// Add rendering hints to smooth text edges
RenderingHints = createObject("java", "java.awt.RenderingHints");
graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
graphics.setRenderingHint( RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY
);

// Create a graphical representation of the text
Font = createObject("java", "java.awt.Font");
textFont = Font.init("Arial", Font.PLAIN, width / 5 );
textColor = createObject("java", "java.awt.Color").decode("##80aa1c");
fontContext = graphics.getFontRenderContext();
layout = createObject("java", "java.awt.font.TextLayout").init(text, textFont, fontContext);

// Center text outline
transX = (width/2) - (layout.getBounds().getWidth()/2);
transY = (height/2) + layout.getDescent();
transform = createObject("java", "java.awt.geom.AffineTransform").init();
transform.setToTranslation( transX, transY );

// Draw the text outline onto the image
shape = layout.getOutline(transform);
graphics.setColor(textColor);
graphics.draw(shape);
graphics.dispose();
</cfscript>

<cfoutput>
<!--- display raw image --->
<cfimage source="#transparentImage#" action="writeToBrowser">
<!--- display WITH background --->
<div style="background-color: ##000080; width: #width#; height=#height#;">
<cfimage source="#transparentImage#" action="writeToBrowser">
</div>
</cfoutput>

4 comments:

Nick,  October 11, 2009 at 9:31 PM  

This is great. Exactly what I wanted.

Thank you so much. Just another thing on it. Is it possible to use this image as a watermark in a pdf using cfpdf action=addwatermark?

cfSearching October 13, 2009 at 12:54 PM  

@Nick,

Try it and find out ;-) But I do not see why not. It is just a transparent image, nothing special.

-Leigh

Nick,  October 14, 2009 at 5:24 PM  

Hi Leigh

cfpdf action=addwatermark didn't seem to work. It puts a gray shaded background instead. Also seems like the text inside is not readable. Looks like a bug in cf8. Not tried on cf9 as yet.

cfSearching October 15, 2009 at 12:01 AM  

@Nick,

I forgot about cfpdf's issues with transparent images. It was fixed in the CF9 beta. For CF8, try using iText

http://cfsearching.blogspot.com/2009/06/cfpdf-issues-when-using-transparent.html

-Leigh

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep