Getting RGBA Pixel Data From ColdFusion Images Using GetPixels()

Posted February 18, 2008 at 7:00 AM by Ben Nadel

Tags: ColdFusion

This weekend, I added two more user defined functions to the ImageUtils.cfc ColdFusion component for image manipulation:

  • GetPixel()
  • GetPixels()

GetPixels() takes a ColdFusion image and returns an array or arrays that contains a structure for each sampled pixel. These structures contain a Red, Green, Blue, Alpha, and Hex key for the Red, Green, Blue, and Alpha channels respectively and the Hex for the 6 digit hexadecimal encoding of the color. By default, if you just pass in the ColdFusion image, GetPixels() will return the pixel data for the entire image. However, you have the option to pass in a starting X and Y coordinate as well as the width and height of the desired sample area:

GetPixels( Image [, X [, Y [, Width [, Height ] ] ] ] ) :: Array

This leverages the underlying Java AWT buffered image object; I am no master of this library, so this might not be the most performant utility function. It will, however, provide me with tools that I will leverage in the authoring of future utility functions.

Let's take a look at an example of how this works. In the demo below, we will take this image:


 
 
 

 
Cute Sexy Blonde Used In The ImageUtils.cfc Examples  
 
 
 

... and sample a 50px by 50px area in the middle of the image. Then, we will loop over that sample and output it in 3 by 3 divs:

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>GetPixels()</title>
  •  
  • <style type="text/css">
  •  
  • div.canvas {
  • background-color: #333333 ;
  • border: 1px solid #000000 ;
  • float: left ;
  • padding: 1px 0px 0px 1px ;
  • }
  •  
  • div.row {
  • clear: both ;
  • height: 3px ;
  • margin-bottom: 1px ;
  • overflow: hidden ;
  • }
  •  
  • div.pixel {
  • float: left ;
  • font-size: 1px ;
  • height: 3px ;
  • line-height: 1px ;
  • margin: 0px 1px 0px 0px ;
  • overflow: hidden ;
  • width: 3px ;
  • }
  •  
  • </style>
  • </head>
  • <body>
  •  
  •  
  • <!--- Create the image utils object. --->
  • <cfset objImageUtils = CreateObject( "component", "imageutilsroot.imageUtils" ).Init() />
  •  
  • <!--- Read in the cute blonde image. --->
  • <cfimage
  • action="read"
  • source="../cute_blonde.jpg"
  • name="objImage"
  • />
  •  
  • <!--- Read in 50 x 50 pixels. --->
  • <cfset arrPixels = objImageUtils.GetPixels(
  • objImage,
  • 200,
  • 185,
  • 50,
  • 50
  • ) />
  •  
  •  
  • <cfoutput>
  •  
  • <div class="canvas">
  •  
  • <!--- Loop or y axis. --->
  • <cfloop
  • index="intY"
  • from="1"
  • to="#ArrayLen( arrPixels )#"
  • step="1">
  •  
  • <div class="row">
  •  
  • <!--- Loop over x axis. --->
  • <cfloop
  • index="intX"
  • from="1"
  • to="#ArrayLen( arrPixels[ intY ] )#"
  • step="1">
  •  
  • <!--- Get the pixel short-hand from the array. --->
  • <cfset objPixel = arrPixels[ intY ][ intX ] />
  •  
  • <div
  • class="pixel"
  • style="background-color: ###objPixel.Hex#;"
  • ></div>
  •  
  • </cfloop>
  •  
  • </div>
  •  
  • </cfloop>
  •  
  • </div>
  •  
  • </cfoutput>
  •  
  • </body>
  • </html>

Running this code, we get the following pixilated output:


 
 
 

 
Cute Sexy Blonde Girl Drawn In 3x3 Pixel Divs  
 
 
 

As you can see, each pixel is drawn using a 3px by 3px div with an explicit background color using the HEX value returned from the function.

Here is the code behind this functionality:

  • <cffunction
  • name="GetPixels"
  • access="public"
  • returntype="array"
  • output="false"
  • hint="Returns a two dimensional array of RGBA values for the image. Array will be in the form of Pixels[ Y ][ X ] where Y is along the height axis and X is along the width axis.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Image"
  • type="any"
  • required="true"
  • hint="The ColdFusion image object whose pixel map we are returning."
  • />
  •  
  • <cfargument
  • name="X"
  • type="numeric"
  • required="false"
  • default="1"
  • hint="The default X point where we will start getting our pixels (will be translated to 0-based system for Java interaction)."
  • />
  •  
  • <cfargument
  • name="Y"
  • type="numeric"
  • required="false"
  • default="1"
  • hint="The default Ypoint where we will start getting our pixels (will be translated to 0-based system for Java interaction)."
  • />
  •  
  • <cfargument
  • name="Width"
  • type="numeric"
  • required="false"
  • default="#ImageGetWidth( ARGUMENTS.Image )#"
  • hint="The width of the area from which we will be sampling pixels."
  • />
  •  
  • <cfargument
  • name="Height"
  • type="numeric"
  • required="false"
  • default="#ImageGetHeight( ARGUMENTS.Image )#"
  • hint="The height of the area from which we will be sampling pixels."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Define the pixels array. --->
  • <cfset LOCAL.Pixels = [] />
  •  
  • <!---
  • Get the image data raster. This holds all the data
  • for the buffered image and will give us access to the
  • individual pixel data.
  • --->
  • <cfset LOCAL.Raster = ImageGetBufferedImage( ARGUMENTS.Image ).GetRaster() />
  •  
  •  
  • <!---
  • Now, starting at our Y position, we want to move down
  • each column (Y-direction) and gather the pixel values
  • for each row along the Y-axis.
  • --->
  • <cfloop
  • index="LOCAL.Y"
  • from="#ARGUMENTS.Y#"
  • to="#(ARGUMENTS.Y + ARGUMENTS.Height - 1)#"
  • step="1">
  •  
  • <!---
  • Create a nested array for this row of pixels.
  • Remember, the data will be in the Pixel[ Y ][ X ]
  • array format.
  • --->
  • <cfset ArrayAppend(
  • LOCAL.Pixels,
  • ArrayNew( 1 )
  • ) />
  •  
  • <!---
  • Loop over this row of pixels at this given Y-axis
  • position.
  • --->
  • <cfloop
  • index="LOCAL.X"
  • from="#ARGUMENTS.X#"
  • to="#(ARGUMENTS.X + ARGUMENTS.Width - 1)#"
  • step="1">
  •  
  • <!---
  • Create an In-array to be used to retreive the
  • color data. Each element will be used for the
  • Red, Green, Blue, and Alpha channels respectively.
  • Be sure to use the JavaCast() here rather than
  • when we pass in the array to the GetPixel()
  • method. This way, we don't lose the reference
  • to the array value.
  • --->
  • <cfset LOCAL.PixelArray = JavaCast(
  • "int[]",
  • ListToArray( "0,0,0,0" )
  • ) />
  •  
  • <!--- Get the pixel data array. --->
  • <cfset LOCAL.Raster.GetPixel(
  • JavaCast( "int", (LOCAL.X - 1) ),
  • JavaCast( "int", (LOCAL.Y - 1) ),
  • LOCAL.PixelArray
  • ) />
  •  
  • <!---
  • Now that we have an index-based pixel data
  • array, let's convert the the data into a keyed
  • struct that will be more user friendly.
  • --->
  • <cfset LOCAL.Pixel = {
  • Red = LOCAL.PixelArray[ 1 ],
  • Green = LOCAL.PixelArray[ 2 ],
  • Blue = LOCAL.PixelArray[ 3 ],
  • Alpha = LOCAL.PixelArray[ 4 ],
  • Hex = ""
  • } />
  •  
  • <!--- Set the HEX value based on the RGB. --->
  • <cfset LOCAL.Pixel.Hex = UCase(
  • Right( "0#FormatBaseN( LOCAL.Pixel.Red, '16' )#", 2 ) &
  • Right( "0#FormatBaseN( LOCAL.Pixel.Green, '16' )#", 2 ) &
  • Right( "0#FormatBaseN( LOCAL.Pixel.Blue, '16' )#", 2 )
  • ) />
  •  
  •  
  • <!--- Add this pixel data to curren row. --->
  • <cfset ArrayAppend(
  • LOCAL.Pixels[ ArrayLen( LOCAL.Pixels ) ],
  • LOCAL.Pixel
  • ) />
  •  
  • </cfloop>
  •  
  • </cfloop>
  •  
  •  
  • <!--- Return pixels. --->
  • <cfreturn LOCAL.Pixels />
  • </cffunction>

In addition to this function, I also created a GetPixel() function that utilizes the GetPixels() function to return the RGBA values for a single pixel:

  • <cffunction
  • name="GetPixel"
  • access="public"
  • returntype="struct"
  • output="false"
  • hint="Returns a struct containing the given pixel RGBA data.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Image"
  • type="any"
  • required="true"
  • hint="The ColdFusion image object whose pixel data map we are returning."
  • />
  •  
  • <cfargument
  • name="X"
  • type="numeric"
  • required="true"
  • hint="The X coordinate of the pixel that we are returning."
  • />
  •  
  • <cfargument
  • name="Y"
  • type="numeric"
  • required="true"
  • hint="The Y coordinate of the pixel that we are returning."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Get the pixels array for a 1x1 area. --->
  • <cfset LOCAL.Pixels = GetPixels(
  • ARGUMENTS.Image,
  • ARGUMENTS.X,
  • ARGUMENTS.Y,
  • 1,
  • 1
  • ) />
  •  
  • <!--- Return the first returned pixel. --->
  • <cfreturn LOCAL.Pixels[ 1 ][ 1 ] />
  • </cffunction>

This returns a single instance of the pixel structure which looks like this:


 
 
 

 
GetPixel() CFDump Output Of RGBA Data For Given Pixel  
 
 
 



Reader Comments

Feb 18, 2008 at 10:44 AM // reply »
50 Comments

That is really cool! (though I can't quite think of a use for it)


Feb 18, 2008 at 10:50 AM // reply »
11,246 Comments

@Steve,

I actually started coding it with another method in mind. Soon, I will come out with TrimCanvas(). This will remove rows and columns from the edges until it hits "real image content". Basically, this is a way of shrinking a canvas to the smallest possible box without cropped image data.

In FireWorks, this would be CTRL+ALT+T. I figure, I can perform this by sampling pixels until I find one that is not the same as the background color.

Now, not sure if that function will have a use, but I think it will, especially when you do a lot of programmatic pasting of images.


Feb 18, 2008 at 10:57 AM // reply »
50 Comments

*that* is nice! (definitely useful)

So you can get the background color of an image separately?

Can you change the background color for index transparency gifs?


Feb 18, 2008 at 11:21 AM // reply »
11,246 Comments

@Steve,

I am not sure if you can get the background color of an image since I don't think that is really stored in any capacity once the background is drawn. I might have to end up passing in the background color as a hint:

TrimCanvas( Image, CanvasColor )

As far as working with transparent GIF images, I guess you could create an alpha canvas and then paste the GIF onto that canvas? I am not exactly sure what you are asking.


Feb 18, 2008 at 11:25 AM // reply »
50 Comments

Sorry, my thought only made sense on the assumption that you could have read and write access to a background color property which doesn't seem to be the case.

Still, very nifty. I am eager to see how it comes out.


Mar 11, 2008 at 4:13 PM // reply »
1 Comments

Sweet hack.


Jan 29, 2010 at 11:35 AM // reply »
8 Comments

Just posted a Tweet asking for help getting the values of pixels from an image.

Knew i should have just looked on here first.

Excellent post.. and exactly what i was looking for


Jan 29, 2010 at 11:37 AM // reply »
11,246 Comments

@Jbuda,

Awesome, glad to help :)


Jan 29, 2010 at 12:19 PM // reply »
8 Comments

Hi Ben

Just looking at the code.. how would i get the dominant colour on the entire image?

Would i just slice the image up and the loop through each fragment and tally up the colours in an struct?

The struct with most colours is the winner?


Jan 29, 2010 at 10:47 PM // reply »
11,246 Comments

@Jbuda,

I am not sure what you mean by dominant color? You mean like between Red, Green, and Blue? Or are you talking about a particular RGB combination?


Mar 30, 2010 at 11:50 AM // reply »
6 Comments

Thanks Ben for this article! GetPixels() helped me to print graphics on a Plastic ID Card Printer. I needed each pixel of the graphic to generate the appropriate hexa code for the printer. The other article about InputBaseN and FormatBaseN was helpful too!


Apr 21, 2010 at 10:01 AM // reply »
11,246 Comments

@Tony,

Awesome my man - glad to help.


Jun 10, 2010 at 6:23 PM // reply »
319 Comments

Has this been testing with CF9? No matter what I use for source images I get:

Coordinate out of bounds!

Line 1225.


Jun 16, 2010 at 9:22 AM // reply »
11,246 Comments

@Raymond,

Hmm, I have not tried this in CF9 yet. I'm having a little trouble getting my CF9 instance to run with certain tags.


Ray
Dec 13, 2010 at 6:16 PM // reply »
2 Comments

Hi,Ben, I currently got an issue on jpg image in CF8. I have a need to convert images from CMYK to RGB but I cant find a easy way.

Thanks,
Ray



Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 24, 2013 at 5:39 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Adam Oops! My mistake! I hadn't gotten that far in my testing - I'm still baby stepping my way through the process. ... read »
May 24, 2013 at 5:13 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
Hi Jason, Thanks for checking up on that, but I still stand firm on my position. :) There are actually two listLast()'s in use, and you're right that the one using a space as a delimiter is fine. ... read »
May 24, 2013 at 4:45 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Ben I have been lurking your site for quite some time, and haven't stepped up to comment until today. Thanks for all the great info - keep it up! @Adam I believe you are mistaken... as the commen ... read »
May 24, 2013 at 11:21 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, Ha ha, let's us never speak of justifying "##" notation again :P ... read »
May 24, 2013 at 11:18 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Ah, so it was indeed how I vaguely remembered it to be: A direct assignment value = users.id[ i ] causes value to retain the sticky datatype of the query column. Although unnecessary in ... read »
May 24, 2013 at 9:11 AM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Brandon, Hi, No, I haven't been able to do that. I have just kept it as it is. ... read »
May 23, 2013 at 9:52 PM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Muhmmadibn Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it. Thanks in advance! ... read »
May 23, 2013 at 6:06 PM
The Girl Who Broke My Heart, And Made Me A Better Person
Good day,ladies and gentle men, my name is Dr AMADI the great spell caster in Africa, i have help so many people for different kind of problems,who say there is no solution to problems on earth, that ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools