Sunday, January 13, 2008

Creating Transparent Gif's with ColdFusion 8 - Part 2

In Part 1 (or as I like to think of it "More than you ever wanted to know about GIF's") we learned how to extract the color components from a GIF's color model, and use the Raster to obtain the color index of a specific pixel. We can now use that information to construct a new color model, but this time with a transparent color.

<cfscript>
// cfSearching: colors[1] - red, colors[2] - green, colors[3] - blue
IndexColorModel = createObject("java", "java.awt.image.IndexColorModel");
newModel = IndexColorModel.init(javacast("int", 8),
javacast("int", colorSize),
colors[1],
colors[2],
colors[3],
javacast("int", transparentColorIndex)
);
</cfscript>
Now that we have a model that supports transparency, we can use it to create our transparent GIF. This is done by creating a new BufferedImage., which is then passed into the ImageNew() function, to create a ColdFusion compatible image object. That is it. We now have a transparent GIF!
<cfscript>
BufferedImage = createObject("java", "java.awt.image.BufferedImage");
newBufferedImage = BufferedImage.init( newModel,
sourceImage.getRaster(),
sourceImage.isAlphaPremultiplied(),
javacast("null", "")
);

transparentImage = ImageNew( newBufferedImage );
</cfscript>

Caveats

Now the thing to remember about GIF's is that they only support fully opaque or fully transparent pixels. So GIF's with curved edges may appear jagged. You can create smoother edges by using anti-aliasing. However, this effect is achieved by blending the edges with the image background color, which results in a faint halo of color around the edges. So you must display the image on a similar background color to maintain the illusion of transparency. This is just the nature of GIF's. The PNG format on the other hand, is able to achieve smooth edges by using variable transparency. By using semi-transparent colors, it creates the illusion of smooth edges without the halo effect.

Finally, the code

This function is rough, but you can test it out using one of the examples below. I have included examples of how to create a new transparent GIF and convert an existing one. Comments, corrections or suggestions are always welcome.

Enjoy!

Use an existing GIF - Example 1
<!--- use the pixel at position x=0, y=0 for the transparent color --->
<cfset sourceImage = ImageNew("https://www.google.com/accounts/reader/screenshot_en.gif")>
<cfset newImage = convertToTransparentGif(source=sourceImage, x=0, y=0)>
<div style="clear: both;">
Use an existing GIF - Example 1<br>
Notice the faint halo of color around the edges due to anti-aliasing<br><br>
<div style="background-color: #ffffcc; padding: 10;float: left;">
<b>Original (opaque)</b><br><br>
<img src="https://www.google.com/accounts/reader/screenshot_en.gif">
</div>
<div style="background-color: #ffffcc; padding: 10;float: right;">
<b>New (transparent, but with anti-aliasing)</b><br><br>
<cfimage action="writeToBrowser" source="#newImage#" format="gif">
</div>
<br><br>
</div>
Use an existing GIF - Example 2
<!--- use the pixel at position x=0, y=0 for the transparent color --->
<cfset sourceImage = ImageNew("http://www.sun.com/software/images/promotions/getjava.gif")>
<cfset newImage = convertToTransparentGif(source=sourceImage, x=0, y=0)>
<div style="clear: both;">
<div style="clear: both;">
Use an existing GIF - Example 2<br>
Notice the faint halo of color around the edges due to anti-aliasing<br><br>
</div>
<div style="background-color: #b0c4de; padding: 10;float: left;">
<b>Original (opaque)</b><br><br>
<img src="http://www.sun.com/software/images/promotions/getjava.gif">
</div>
<div style="background-color: #f5f5f5; padding: 10;float: left;">
<b>New (transparent, but with anti-aliasing)</b><br><br>
<cfimage action="writeToBrowser" source="#newImage#" format="gif">
</div>
</div>
Create a New Transparent GIF - Example 1
<!--- cfSearching: Note the type is *ARGB* and anti-aliasing is ON --->
<cfset imageBackgroundColor = "##ffffff">
<cfset img = ImageNew("", 400, 200, "argb", imageBackgroundColor)>
<cfset ImageSetAntiAliasing(img, "ON")>
<cfset ImageSetDrawingColor(img, "##336600")>
<cfset ImageDrawRoundRect(img, 125, 26, 200, 99, 20, 20, "true")>
<cfset ImageSetDrawingColor(img,"##003366")>
<cfset fontAttr = StructNew()>
<cfset fontAttr.font = "Arial">
<cfset fontAttr.size = 32>
<cfset fontAttrr.style = "bold">
<cfset ImageDrawText(img, "Can you see ..", 65, 80, fontAttr)>
<cfset ImageSetDrawingColor(img, "##003366")>
<cfset ImageDrawRoundRect(img, 32, 90, 200, 99, 20,20, "true")>
<cfset ImageSetDrawingColor(img,"##336600")>
<cfset ImageDrawText(img, "right through me?", 92, 155, fontAttr)>

<cfset newImage = convertToTransparentGif(img, imageBackgroundColor)>
<cfset ImageWrite(newImage, ExpandPath("newGifExample1.gif"))>
<div style="clear: both;">
Create a new GIF - Example 1<br>
Notice the faint halo of color around the edges due to anti-aliasing<br><br>
</div>
<br>
<div style="background-color: #f5f5f5; width: 400; height: 200;">
<cfimage action="writeToBrowser" source="#newImage#" format="gif">
</div>
Create a New Transparent GIF - Example 2
<!--- cfSearching: Note the type is *RGB* and anti-aliasing is OFF --->
<cfset img = ImageNew("", 400, 200, "rgb")>
<cfset ImageSetAntiAliasing(img, "OFF")>
<cfset ImageSetDrawingColor(img, "##336600")>
<cfset ImageDrawRoundRect(img, 125, 26, 200, 99, 20, 20, "true")>
<cfset ImageSetDrawingColor(img,"##003366")>
<cfset fontAttr = StructNew()>
<cfset fontAttr.font = "Arial">
<cfset fontAttr.size = 32>
<cfset fontAttrr.style = "bold">
<cfset ImageDrawText(img, "Can you see ..", 65, 80, fontAttr)>
<cfset ImageSetDrawingColor(img, "##003366")>
<cfset ImageDrawRoundRect(img, 32, 90, 200, 99, 20, 20, "true")>
<cfset ImageSetDrawingColor(img,"##336600")>
<cfset ImageDrawText(img, "right through me?", 92, 155, fontAttr)>

<cfset newImage = convertToTransparentGif(source=img, x=0, y=0)>
<cfset ImageWrite(newImage, ExpandPath("newGifExample2.gif"))>
<div style="clear: both;">
Create a new GIF - Example 2<br>
Notice the edges are jagged because we did not use anti-aliasing<br><br>
</div>
<br>
<div style="background-color: #b0c4de; width: 400; height: 200;">
<cfimage action="writeToBrowser" source="#newImage#" format="gif">
</div>
ConvertToTransparentGif Function
Based on code from PaulFMendler
http://forum.java.sun.com/thread.jspa?forumID=20&threadID=425160
<cffunction name="convertToTransparentGif" returntype="any" access="public" output="true"
hint="Converts a ColdFusion image, or BufferedImage object, into a transparent GIF">

<cfargument name="source" type="any" required="true" hint="A ColdFusion image object or a BufferedImage">
<cfargument name="color" type="string" required="false" default="" hint="The transparent color in hexadecimal format">
<cfargument name="x" type="numeric" required="false" default="-1" hint="Use the pixel at position x,y as the transparent color">
<cfargument name="y" type="numeric" required="false" default="-1" hint="Use the pixel at position x,y as the transparent color">
<cfset Local = structNew()>

<!--- cfSearching: validate that a valid image object was supplied --->
<cfif NOT IsImage(arguments.source) AND NOT IsInstanceOf(arguments.source, "java.awt.image.BufferedImage")>
<cfthrow message="Invalid argument. Source must be a ColdFusion image or a BufferedImage">
</cfif>

<!--- cfSearching: verify the correct transparency arguments were supplied --->
<cfset Local.transparentColor = replace(trim(arguments.color), "##", "", "all")>
<cfif Local.transparentColor eq "" AND arguments.x EQ -1 AND arguments.y EQ -1>
<cfthrow message="Missing argument. You must supply either a transparent color OR the X and Y coordinates of the transparent pixel.">

<cfelseif Local.transparentColor neq "" AND (arguments.x NEQ -1 OR arguments.y NEQ -1)>
<cfthrow message="Too many arguments. Supply EITHER a transparent color OR the X and Y coordinates of the transparent pixel.">
</cfif>

<cfset Local.useTransparentColor = len(Local.transparentColor)>
<cfif Local.useTransparentColor>
<!--- cfSearching: transparent color must be a 6 character hex value --->
<cfif len(Local.transparentColor) NEQ 6 OR ReFindNoCase("[^0-9a-f]", Local.transparentColor)>
<cfthrow message="Invalid argument. Transparent color must be a 6 character hexidecimal value.">
</cfif>
<cfelse>
<!--- cfSearching: x,y coordinates must be >= zero --->
<cfif arguments.x LTE -1 OR arguments.y LTE -1>
<cfthrow message="Invalid or missing argument. The minimum value for X and Y coordinates is 0.">
</cfif>
</cfif>

<cfscript>
Local.sourceImage = arguments.source;

// cfSearching: extract the BufferedImage if needed
if ( IsImage(arguments.source) ) {
Local.sourceImage = ImageGetBufferedImage(arguments.source);
}

// cfSearching: encode the image as a gif to create the correct color model
if ( NOT IsInstanceOf(Local.sourceImage.getColorModel(), "java.awt.image.IndexColorModel")) {
Local.ImageIO = createObject("java", "javax.imageio.ImageIO");
Local.bao = createObject("java", "java.io.ByteArrayOutputStream").init();
Local.ImageIO.write( Local.sourceImage, "gif", Local.bao);
Local.bai = createObject("java", "java.io.ByteArrayInputStream").init( Local.bao.toByteArray() );
Local.sourceImage = Local.ImageIO.read( Local.bai );
}

// cfSearching: extract the color model information
Local.sourceModel = Local.sourceImage.getColorModel();
Local.colorSize = Local.sourceModel.getMapSize();

// cfSearching: Construct byte array to store rgb component values from the color model
// cfSearching: Based on source from Christian Cantrell's blog http://weblogs.macromedia.com/cantrell/archives/2004/01/byte_arrays_and_1.cfm
Local.byteClass = createObject("java", "java.lang.Byte").TYPE;
Local.reflectArray = createObject("java","java.lang.reflect.Array");
Local.dimen = [ 3, Local.colorSize ];
Local.colors = Local.reflectArray.newInstance( Local.byteClass, javacast("int[]", Local.dimen) );

// cfSearching: Grab the rgb color components from source color model
// cfSearching: colors[1] - red, colors[2] - green, colors[3] - blue
Local.sourceModel.getReds( Local.colors[1] );
Local.sourceModel.getGreens( Local.colors[2] );
Local.sourceModel.getBlues( Local.colors[3] );

// cfSearching: initialize the transparent color index to "none"
Local.transIndex = -1;

if (Local.useTransparentColor) {
// cfSearching: Extract decimal RGB values of transparent color
Local.transRed = InputBaseN( Left(Local.transparentColor, 2), 16);
Local.transGreen = InputBaseN( Mid(Local.transparentColor, 3, 2), 16);
Local.transBlue = InputBaseN( Right(Local.transparentColor, 2), 16);

// cfSearching: Find the index of the transparent color in the source color model
for (Local.index = 1; Local.index LTE Local.colorSize; Local.index = Local.index + 1) {

// cfSearching: RGB color component arrays should all be the same size
Local.currRed = BitAnd( Local.colors[1][Local.index], 255);
Local.currGreen = BitAnd( Local.colors[2][Local.index], 255);
Local.currBlue = BitAnd( Local.colors[3][Local.index], 255);

if ( (Local.currRed EQ Local.transRed) AND (Local.currGreen EQ Local.transGreen)
AND (Local.currBlue EQ Local.transBlue)) {

// cfSearching: must subtract 1 because java arrays are zero based
Local.transIndex = Local.index - 1;
break;
}
}
}
else {
// cfSearching: Otherwise, use the pixel at the given coordinates as the transparent color
Local.transIndex = Local.sourceImage.getRaster().getSample(
javacast("int", arguments.x),
javacast("int", arguments.y),
javacast("int", 0)
);
}

// cfSearching: create new color model with transparent color
Local.IndexColorModel = createObject("java", "java.awt.image.IndexColorModel");
Local.newModel = Local.IndexColorModel.init(javacast("int", 8),
javacast("int", Local.colorSize),
Local.colors[1],
Local.colors[2],
Local.colors[3],
javacast("int", Local.transIndex)
);

Local.BufferedImage = createObject("java", "java.awt.image.BufferedImage");
Local.newBufferedImage = Local.BufferedImage.init(
Local.newModel,
Local.sourceImage.getRaster(),
Local.sourceImage.isAlphaPremultiplied(),
javacast("null", "")
);
</cfscript>

<cfreturn ImageNew( Local.newBufferedImage )>

</cffunction>

4 comments:

Todd Sharp January 15, 2008 at 8:16 AM  

Hey I just found your blog via another post on cfbloggers.org - but I notice you don't have an about page (or even a byline for that matter). So...who are you?? :D

cfSearching January 15, 2008 at 12:29 PM  

Eh, I am not one of the heavy hitters. So I did not think an about me section was warranted. That and I loathe writing profiles, and rarely read them ;) One day when I am running the world with my ColdFusion applications, I will hire a professional to spin a suitably fascinating and impressive profile. But I would not hold your breath waiting for that to happen ;)

randhawaz.com May 13, 2010 at 9:38 AM  

Hi.. I was in the middle of something when i thought of one thing..

Suppose i upload an Image. the image with the cartoon on it.. I want that the background of the image should be black or any other i specify leaving that cartton over ir. The background should change. I am also using the watremak on it..

So here is my part of work i did. i am not an expert in handling image functions but it may involve advance java i thought. so i thought i share with you i tried something like this:










I tried using the ImageSetBackgroundColor with ImageRect Function but that did not effected. Is there any way of doing it

Cheers

cfSearching May 13, 2010 at 10:04 AM  

@randhawaz.com,

If you want to post a small amount of code, it has to be html escaped ie replace < and > symbols with &lt; and &gt;

-Leigh

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep