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 FunctionBased 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>
...Read More