Creating JPG Images with Rounded Corners

Rounded corners seem to be the next "big" thing for web designers, every damn box or image has to have a rounded corner. The problem is of course if you are using a content management system you can't possibly expect the users to first add rounded corners to the image before uploading it.

Thankfully there are a few different solutions, you could buy Image Effects, you could use ImageDrawRoundRect() function as shown by Gareth, or use a Java solution as shown on cfSearching.

If you want to do more than just rounded corners with your images it may well be worth forking out the $50 bucks for the Image Effects component from Foundeo.

CF8 ImageDrawRoundRect()

I tested the method by Gareth and found that the quality of the image even after setting the image quality to 100% produced image aberrations in the corners of the produced jpg image.

Java, but PNG Only

I also tested the Java method by leigh at cfSearching and was impressed with the quality of the image, the only problem was that it only worked with png images and I needed it to work with JPG.

Java, JPG Rounded Corners

How do you go from a png to a jpg and keep the rounded corners? Simply converting the image using either ColdFusion or native Java won't work it will just drop the alpha channel and you will be left with your square image.

After doing some Googling I found something that could just work. The idea was to take an image of TYPE_INT_ARGB and lay it on top of an image of TYPE_INT_RGB.

Using the example code from cfSearching as a base I came up with the following function.


<cffunction name="applyRoundedCorners" returntype="any" access="public" output="true">
<cfargument name="imgObj" type="any" required="true" hint="the image object read with the imageRead() method">
<cfargument name="arcWidth" type="numeric" required="true" hint="horizontal diameter of the rounded corners">
<cfargument name="arcHeight" type="numeric" required="true" hint="vertical diameter of the rounded corners">

<cfscript>
var Local = structNew();
Local.theImage = 0;
//create required java objects
Local.Color = createObject("java", "java.awt.Color");
Local.AlphaComposite = createObject("java", "java.awt.AlphaComposite");

Local.sourceImage = ImageGetBufferedImage(arguments.imgObj);
Local.width = Local.sourceImage.getWidth();
Local.height = Local.sourceImage.getHeight();
// create a bufferedImage to hold the mask
Local.BufferedImage = createObject("java", "java.awt.image.BufferedImage");
Local.Mask = Local.BufferedImage.init( javacast("int", Local.width),
javacast("int", Local.height),
Local.BufferedImage.TYPE_INT_ARGB
);
Local.graphics = Local.Mask.createGraphics();

// use anti-aliasing for smoother edges
Local.RenderingHints = createObject("java", "java.awt.RenderingHints");
Local.graphics.setRenderingHint( Local.RenderingHints.KEY_ANTIALIASING,
Local.RenderingHints.VALUE_ANTIALIAS_ON
);
//create a rounded rectangle mask
Local.graphics.setColor( Local.Color.white);
Local.graphics.fillRoundRect(javacast("double", 0),
javacast("double", 0),
javacast("double", Local.width),
javacast("double", Local.height),
javacast("double", arguments.arcWidth),
javacast("double", arguments.arcHeight)
);
//draw the source image onto the mask
Local.graphics.setComposite( Local.AlphaComposite.SrcIn );
//Local.graphics.draw(Local.rr);
Local.graphics.drawImage( Local.sourceImage,
javacast("int", 0),
javacast("int", 0),
javacast("null", "")
);
Local.graphics.dispose();
     //check to see if the source image is a jpg, if it is we need to lay the png onto the jpg and return that.
if (listlast(arguments.imgObj.source,'.') eq 'jpg'){
        //create second image, this will be the jpg image
Local.BufferedImageRGB = createObject("java", "java.awt.image.BufferedImage");
Local.MaskRGB = local.BufferedImageRGB.init(javacast("int", Local.width),
javacast("int", Local.height),
Local.BufferedImageRGB.TYPE_INT_RGB
);
Local.GraphicRGB = local.MaskRGB.createGraphics();
/*fill the jpg image with white background color - we could expand the component and
pass the fill in as an argument*/

Local.GraphicRGB.setColor(local.color.white);
Local.GraphicRGB.fillRect(javacast("double", 0),
javacast("double", 0),
javacast("double", Local.width),
javacast("double", Local.height));
local.GraphicRGB.drawrenderedimage(local.mask,javacast("null",""));
local.GraphicRGB.dispose();
Local.theImage = Imagenew(local.MaskRGB);
}else{
Local.theImage = Imagenew(local.Mask);
}
</cfscript>

<cfreturn Local.theImage>
</cffunction>

You call the function as follows:


<cfset myimage = imageRead("C:\testblack.jpg")>
<cfset myimage = applyRoundedCorners( myimage, 20, 20 )>
<cfimage source="#myimage#" action="WriteToBrowser">
<cfimage source="#myimage#" action="write" destination="C:\test.jpg" overwrite="true">

Original Image:

Image with Rounded Corners:

I have tested it in both Railo and CF9, if you notice any problems please let me know.

Happy coding...

6 Comments to "Creating JPG Images with Rounded Corners"- Add Yours
Leigh's Gravatar Gary,

Okay. You asked for comments ... so now you are in for it ;)

1) How did you get the smoother edges for the last jpeg above? Using the sample code, mine are very ragged. Though, ragged edges are actually what I expected due to the lack of transparency.

2) What jvm are you using? Because I am seeing very different results under CF9. While it makes sense the alpha channel would be problematic when converting from one format to another, I cannot seem to reproduce the all black image problem with .jpg + original function. (I must be missing something) Would you mind posting a snippet that produces those results, as you have got me very curious.

3) I think there are two small typos in the code

<cfset myimage = applyRoundedCorner( myimage, 20, 20 )>
..
if (listlast(source.source,'.') eq 'jpg'){

Should be:

<cfset myimage = applyRoundedCorners( myimage, 20, 20 )>
..
if (listlast(arguments.source,'.') eq 'jpg'){

-Leigh
# Posted By Leigh | 1/11/10 7:57 AM
Leigh's Gravatar (BTW: If you already received a similar message, sorry for the duplication. My connectivity was terrible the other day, so I was not sure if it actually went through.)
# Posted By Leigh | 1/11/10 7:59 AM
Gary's Gravatar Hi Leigh,

I corrected the first typo thanks, but the second, source.source should read "arguments.source.source". The image comes in as a structure. Arguments.source is the image objects which contains a key called source which contains the path to the source image. I could have probably used something else but that was the first thing I saw.

The smooth edges are provided by using the anti-aliasing hint.

I tested both on CF9 and Railo 3.1.2.001 final, the image in my blog is produced on Railo.
# Posted By Gary | 1/12/10 9:29 AM
Leigh's Gravatar Hi Gary,

Thanks for clearing that up. It was a stupid pilot error. When I replaced "source.source", I overlooked the fact that in your function "source" is an image object (not a path). So while arguments.source seemed to work, it did not really. CF9 was probably using arguments.source.toString() implicitly, instead of throwing an error like Railo. So that is why the corners in my final image were ragged (in CF9). Using arguments.source.source, it works perfectly. Mystery solved ;)

-Leigh
# Posted By Leigh | 1/12/10 9:41 PM
Gary Gilbert's Gravatar Hi Leigh,

I admit the argument name is crappy, I have changed it to imgObj to make it less confusing.
# Posted By Gary Gilbert | 1/13/10 9:51 AM
Leigh's Gravatar Hi Gary,

No problem. It was mainly a mistake on my part. But that name is better (more intuitive). It works beautifully now. Good job.

-Leigh
# Posted By Leigh | 1/13/10 4:39 PM

Powered By Railo

Subscribe

Subscribe via RSS
Follow garyrgilbert on Twitter Follow me on Twitter
Or, Receive daily updates via email.

Tags

adobe air ajax apple cf community cfml coldfusion examples ext flash flex google javascript max2007 max2008 misc open source programming railo software technology ui

Recent Entries

Converting structkeys to lowercase

Blogroll

An Architect's View
CFSilence
Rey Bango
TalkingTree

Wish List

My Amazon.com Wish List