Here's another look at creating image histograms in ColdFusion, this time using JAI (Java Advanced Imaging).
With CFImageHistogram I decided not to use JAI because I didn't want the CFC to require any external Java libraries not currently available to CF6/CF7.
Since speed is definitely an issue with CFImageHistogram I thought I would post some example of code that uses JAI. JAI is much fast at producing image histograms than my method of inspecting pixels in a bufferedimage; however, this method requires that JAI be in the class path of your CF server.
Another advantage of using JAI is that you can get the histograms of more file types (jpg,bmp,tif)
Here is a basic example of using JAI to create a histogram:
<cfscript>
//image file name
filename = expandPath('01.JPG');
//create JAI object
JAI = createObject("Java","javax.media.jai.JAI");
//create JAI PlanarImage
image = JAI.create("fileload", filename);
//create ParameterBlock
pb = createObject("Java","java.awt.image.renderable.ParameterBlock").init();
//add image source
pb.addSource(image);
//RenderedOp
op = JAI.create("histogram", pb);
//get histogram (javax.media.jai.Histogram)
histogram = op.getProperty("histogram");
</cfscript>
To get the histogram array[3][256]:
histogram.getBins()
To get the mean:
histogram.getMean()
Finally here is some code to create an color image histogram png file:
<cfscript>
//image file name
filename = expandPath('01.JPG');
//create JAI object
JAI = createObject("Java","javax.media.jai.JAI");
//create JAI PlanarImage
image = JAI.create("fileload", filename);
//create ParameterBlock
pb = createObject("Java","java.awt.image.renderable.ParameterBlock").init();
//add image source
pb.addSource(image);
//RenderedOp
op = JAI.create("histogram", pb);
//get histogram (javax.media.jai.Histogram)
histogram = op.getProperty("histogram");
//histogram bins - array[3][256]
bins = histogram.getBins();
//set image height
histogramImageHeight = 100;
//get PNG buffered image to draw on
pngBuffered = getPNGBufferedImage(histogramImageHeight);
//get java.awt.Graphics2D
Graphics2D = pngBuffered.getGraphics();
//set background color to white
setImageBackgroundColor(255,255,255);
Graphics2D.setBackground(variables.imageBackgroundColor);
Graphics2D.clearRect(0,0,256,histogramImageHeight);
//set colors with alpha of 33% (85/255)
red = createObject("java", "java.awt.Color").init(javaCast("int",255),javaCast("int",0),javaCast("int",0),javaCast("int",85));
green = createObject("java", "java.awt.Color").init(javaCast("int",0),javaCast("int",255),javaCast("int",0),javaCast("int",85));
blue = createObject("java", "java.awt.Color").init(javaCast("int",0),javaCast("int",0),javaCast("int",255),javaCast("int",85));
//loop thru color bins r-g-b
for(i=1;i LTE 3; i=i+1) {
//set color based on bin
if (i eq 1) {//red
currentColor = red;
} else if (i eq 2) {//green
currentColor = green;
} else if (i eq 3) {//blue
currentColor = blue;
}
//set drawing color
Graphics2D.setPaint(currentColor);
//loop thru all bins in color
for(ii=1;ii LTE 256; ii=ii+1) {
//set as 0 so we get a line not a rectangle
rectWidth = javaCast("int",0);
//set max height based on the max size found in array
rectHeight = javaCast("int",(bins[i][ii]/arrayMax(bins[1])) * histogramImageHeight);
//X left to right 0-255
rectX = javaCast("int",ii-1);
//switch Y (vertical) so that lines sit on bottom
rectY = javaCast("int",histogramImageHeight-rectHeight);
//create rectangle that is actually a line becasue we gave it no width
rect = createObject("java", "java.awt.Rectangle").init(rectX,rectY,rectWidth,rectHeight);
//draw line
Graphics2D.draw(rect);
}
}
writePNGImage(expandpath("hist.png"),pngBuffered);
</cfscript>
<cffunction name="getPNGBufferedImage" access="private" output="false" hint="I draw a histogram image">
<cfargument name="height" required="yes" type="numeric" hint="height of png to create" />
<cfscript>
var width = 256;
//create bufferedimage object
var bufferedImage = createObject("java", "java.awt.image.BufferedImage");
//create PNG based on width height
bufferedImage.init(JavaCast("int", width), JavaCast("int", arguments.height), BufferedImage.TYPE_4BYTE_ABGR);
//return the buffered image
return bufferedImage;
</cfscript>
</cffunction>
<cffunction name="writePNGImage" access="private" output="false" hint="I write a PNG file to disk">
<cfargument name="outputHistogramImage" required="yes" type="string" hint="filename" />
<cfargument name="pngBuffered" required="yes" hint="buffered PNG" />
<cfscript>
//write PNG file
var outputStream = createObject("java", "java.io.File").init(arguments.outputHistogramImage);
var ImageIO = createObject("java", "javax.imageio.ImageIO");
ImageIO.write(arguments.pngBuffered, "png", outputStream);
</cfscript>
</cffunction>
<cffunction name="setImageBackgroundColor" access="public" returntype="void" output="false" hint="I set the the buffered image, i can be used if you want to set a buffered image instead of specifying a image file name and path">
<cfargument name="red" required="yes" default="255" type="numeric" hint="red color value 0-255" />
<cfargument name="green" required="yes" default="255" type="numeric" hint="green color value 0-255" />
<cfargument name="blue" required="yes" default="255" type="numeric" hint="blue color value 0-255" />
<cfargument name="alpha" required="yes" default="255" type="numeric" hint="alpha color value 0-255" />
<cfset variables.imageBackgroundColor = createObject("java", "java.awt.Color").init(javaCast("int",arguments.red),javaCast("int",arguments.green),javaCast("int",arguments.blue),javaCast("int",arguments.alpha)) />
</cffunction>
<img src="hist.png" style="border:black 1px solid;margin:4px;" />
The above code may get mangled in my small code view so here's a text version
Example Input JPG:
Example Output histogram PNG: