Monday, December 31, 2007

Create and Resize Completely Transparent GIF's

While working on the last entry about images with rounded corners I happened to do some searching on transparent GIF's. A google search turned up an entry on Ben Nadel's blog that mentioned a possible bug involving completely transparent GIF's. The bug occurs when you resize the totally transparent gif with with ColdFusion and then try to write the image back to the file system. I tested it and sure enough the code threw an exception.


An exception occured while trying to write the image.
Ensure that the destination directory exists and that Coldfusion has permission to write to the given path or file. cause : java.lang.NullPointerException

java.lang.NullPointerException
at com.sun.media.imageioimpl.common.PaletteBuilder.findPaletteEntry(PaletteBuilder.java:349)
...


It does appear to be a bug, but apparently one with the JDK, not ColdFusion: Writing an empty ARGB BufferedImage using GIF Writer throws NullPointerException.


Curious, I decided to investigate whether there was any way to resize and write a completely transparent gif to disk. I did not have much luck with ColdFusion functions or ARGB BufferedImages. But I did find a way to create a 100% transparent gif that would work in a pinch. It seems to work well, but I have not done extensive testing, so use it at your own risk ;)

Where to begin


I first created a transparent color model. To keep it simple the color model supports 'black' only. This was achieved by creating three byte arrays. The byte arrays represent the RGB values for the color black: ie Color(0,0,0) or #000000 in hex.


<!--- cfSearching: note, these color arrays support only 'black' --->
<cfset Local.redArray = javacast("byte[]", listToArray("0,0"))>
<cfset Local.greenArray = javacast("byte[]", listToArray("0,0"))>
<cfset Local.blueArray = javacast("byte[]", listToArray("0,0"))>


Now its worth noting that using a 'black' only color model means all text and shapes added to this image would appear in black only. But this is fine since we only want to create a completely clear gif, not draw on a clear background.

Next the arrays are passed into one of the IndexColorModel constructors.
The first parameter represents the number of bits each pixel occupies. The second is the size of the rbg color arrays. Their sizes should be equal. The last parameter represents the transparency: 0.


<!--- cfSearching: create transparent color model --->
<cfset Local.IndexColorModel = createObject("java", "java.awt.image.IndexColorModel")>
<cfset Local.colorModel = Local.IndexColorModel.init( javacast("int", 1),
arrayLen(Local.redArray),
Local.redArray,
Local.greenArray,
Local.blueArray, 0)>


The color model is then used to create a byte indexed BufferedImage. Finally the BufferedImage is passed to the ImageNew function to return a CF compatible image object.


<cfset Local.img = Local.BufferedImage.init(
javacast("int", arguments.width),
javacast("int", arguments.height),
Local.BufferedImage.TYPE_BYTE_INDEXED,
Local.colorModel)>
<cfreturn ImageNew(Local.img)>


Voila! We now have a completely transparent GIF that can be written to disk by ColdFusion. Bear in mind I am still learning about color models, so any corrections, comments or suggestions are welcome.

Complete Code

<h1>Create Completely Transparent GIF Example </h1>
<!--- cfSearching: initialize image settings --->
<cfset width = javacast("int", 200)>
<cfset height = javacast("int", 200)>
<cfset fileNameOfSavedImage = "newTransparentGif.gif">

<!--- cfSearching: create a totally transparent gif and save it to disk --->
<cfset clear = createClearGif(width, height)>
<cfset ImageWrite(clear, ExpandPath(fileNameOfSavedImage))>

<!--- cfSearching: display the saved gif --->
<cfoutput>
<div style="background: url(#fileNameOfSavedImage#);">
<div style="font-family: verdana,arial;">
<b>Is this image really transparent?</b>
</div>
</div>
</cfoutput>


<cffunction name="createClearGif" returntype="any" access="public" output="false">
<cfargument name="width" type="numeric" required="true">
<cfargument name="height" type="numeric" required="true">

<cfset var Local = structNew()>

<!--- cfSearching: note, these color arrays support only 'black' --->
<cfset Local.redArray = javacast("byte[]", listToArray("0,0"))>
<cfset Local.greenArray = javacast("byte[]", listToArray("0,0"))>
<cfset Local.blueArray = javacast("byte[]", listToArray("0,0"))>

<!--- cfSearching: create transparent color model --->
<cfset Local.IndexColorModel = createObject("java", "java.awt.image.IndexColorModel")>
<cfset Local.colorModel = Local.IndexColorModel.init( javacast("int", 1),
javacast("int", arrayLen(Local.redArray)),
Local.redArray,
Local.greenArray,
Local.blueArray,
javacast("int", 0))>

<!--- cfSearching: create new image using the color model --->
<cfset Local.BufferedImage = createObject("java", "java.awt.image.BufferedImage")>
<cfset Local.img = Local.BufferedImage.init(
javacast("int", arguments.width),
javacast("int", arguments.height),
Local.BufferedImage.TYPE_BYTE_INDEXED,
Local.colorModel)>

<!--- cfSearching: return the image in CF compatible format --->
<cfreturn ImageNew(Local.img)>
</cffunction>

0 comments:

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep