Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with:

Pixelating An Image With ColdFusion

By Ben Nadel on
Tags: ColdFusion

Last night, I was building some internal company tools for prototyping when I needed to access the color of an area of a given image (in order to programmatically set a background color). While ColdFusion does not make this information available directly, it does provide a way to access the underlying buffered image of the ColdFusion image object. The buffered image is the collection of pixel data that represents the current image. Once we have access to the actual pixel data, we can really start to monkey around with the image.

For fun, I thought I would try to create a "pixelation" effect in which you can take a ColdFusion image and turn it into a series of larger pixels (abstractly speaking - in practice, pixels are still only 1x1 squares). The strategy behind this effect is one of averaging: to create a "single" pixel out of a 20x20 area of the image, I have to get all of the pixel values in that 20x20 area and then average their colors together.

The one caveat here is that the buffered image returns pixel color values as a single integer. When we average the colors together, we can't just average this value since it's not a "true" number; rather, it's a collection of bits that represent the Red, Green, Blue (and sometimes Alpha) channels of the given pixel color. As such, we need to extract and average each channel individually, and then merge them back together to create another single-integer representation.

I have taken this algorithm and wrapped it up in the following function, imagePixelate():

  • <cffunction
  • name="imagePixelate"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I take a ColdFusion image object and pixelate it with the given pixel size. NOTE: To keep inline with the exisitng image functions, this will work directly on the existing image (rather than creating a new image).">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="image"
  • type="any"
  • required="true"
  • hint="I am the image being pixelated."
  • />
  •  
  • <cfargument
  • name="pixelSize"
  • type="numeric"
  • required="false"
  • default="20"
  • hint="I am size of the pixels to use in the effect."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!---
  • Get the underlying buffered image. This is going to give
  • us access to the individual pixels that make up the image.
  • --->
  • <cfset local.bufferedImage = imageGetBufferedImage(
  • arguments.image
  • ) />
  •  
  • <!---
  • Store the image dimensions - these will be referenced a
  • lot, so let's create a short hand.
  • --->
  • <cfset local.imageWidth = imageGetWidth( arguments.image ) />
  • <cfset local.imageHeight = imageGetHeight( arguments.image ) />
  •  
  • <!---
  • Loop over the image, examining a pixel chunk at a time.
  • Each chunk will be the NxN pixel area size.
  • --->
  • <cfloop
  • index="local.x"
  • from="0"
  • to="#(local.imageWidth - 1)#"
  • step="#arguments.pixelSize#">
  •  
  • <cfloop
  • index="local.y"
  • from="0"
  • to="#(local.imageHeight - 1)#"
  • step="#arguments.pixelSize#">
  •  
  •  
  • <!---
  • When grabbing the given area of pixels, we have
  • to be sure not to go out of bounds on the image.
  • As such, we cannot rely on the pixelSize to be
  • available (this becomes likely only the last
  • row and / or column).
  • --->
  • <cfset local.pixelWidth = min(
  • arguments.pixelSize,
  • (local.imageWidth - local.x)
  • ) />
  •  
  • <cfset local.pixelHeight = min(
  • arguments.pixelSize,
  • (local.imageHeight - local.y)
  • ) />
  •  
  • <!---
  • Grab the collection of pixels from the
  • underlying buffered image. This will return a
  • single-dimension array of pixel values.
  • --->
  • <cfset local.pixelBuffer = local.bufferedImage.getRGB(
  • javaCast( "int", local.x ),
  • javaCast( "int", local.y ),
  • javaCast( "int", local.pixelWidth ),
  • javaCast( "int", local.pixelHeight ),
  • javaCast( "null", "" ),
  • javaCast( "int", 0 ),
  • javaCast( "int", local.pixelWidth )
  • ) />
  •  
  • <!---
  • Now that we have the colors, we want to average
  • them together. However, we can't simply use the
  • integer to average; rather, we have to pick out
  • the individual R,G,B channels and average them
  • seperately.
  • --->
  • <cfset local.red = 0 />
  • <cfset local.green = 0 />
  • <cfset local.blue = 0 />
  •  
  • <!---
  • Loop over each pixel color value in our current
  • pixel buffer.
  • --->
  • <cfloop
  • index="local.pixelColor"
  • array="#local.pixelBuffer#">
  •  
  • <!---
  • Using Bit-wise SHIFTing and ANDing, get
  • each of the color channels and add it to
  • the running sum.
  •  
  • NOTE: I am not taking into account the ALPHA
  • channel which would require another 24 bit
  • right-shift.
  • --->
  •  
  • <!--- Red. --->
  • <cfset local.red += bitAnd(
  • bitSHRN( local.pixelColor, 16 ),
  • 255
  • ) />
  •  
  • <!--- Green. --->
  • <cfset local.green += bitAnd(
  • bitSHRN( local.pixelColor, 8 ),
  • 255
  • ) />
  •  
  • <!--- Blue. --->
  • <cfset local.blue += bitAnd(
  • local.pixelColor,
  • 255
  • ) />
  •  
  • </cfloop>
  •  
  • <!---
  • Now that we have summed the R,G,B channels, we can
  • average them together.
  • --->
  •  
  • <!--- Red. --->
  • <cfset local.redAverage = fix(
  • local.red / arrayLen( local.pixelBuffer )
  • ) />
  •  
  • <!--- Green. --->
  • <cfset local.greenAverage = fix(
  • local.green / arrayLen( local.pixelBuffer )
  • ) />
  •  
  • <!--- Blue. --->
  • <cfset local.blueAverage = fix(
  • local.blue / arrayLen( local.pixelBuffer )
  • ) />
  •  
  • <!---
  • Now that we have averaged the individual colors,
  • we need to merge them together to create a full,
  • RGB integer value.
  •  
  • NOTE: Again, I am not taking the ALPHA channel
  • into account.
  • --->
  • <cfset local.pixelAverage = bitOr(
  • bitOr(
  • bitSHLN( local.redAverage, 16 ),
  • bitSHLN( local.greenAverage, 8 )
  • ),
  • local.blueAverage
  • ) />
  •  
  • <!---
  • Now that we have the average, we want to
  • overwrite every array value in the pixel buffer
  • with the given, average color. However, since
  • ColdFusion doesn't play nicely with true Java
  • arrays, we'll create our own arrray.
  • --->
  • <cfset local.averagePixelBuffer = [] />
  •  
  • <!---
  • Set NxN indicies of the ColdFusion array with the
  • pixel color average (we will convert it to a Java
  • array in the succeeding step).
  • --->
  • <cfset arraySet(
  • local.averagePixelBuffer,
  • 1,
  • (local.pixelWidth * local.pixelHeight),
  • local.pixelAverage
  • ) />
  •  
  • <!---
  • Now that we have our new average RGB color array,
  • let's write it back to the buffered image.
  •  
  • NOTE: We are casting our ColdFusion array to a
  • Java int array.
  • --->
  • <cfset local.bufferedImage.setRGB(
  • javaCast( "int", local.x ),
  • javaCast( "int", local.y ),
  • javaCast( "int", local.pixelWidth ),
  • javaCast( "int", local.pixelHeight ),
  • javaCast( "int[]", local.averagePixelBuffer ),
  • javaCast( "int", 0 ),
  • javaCast( "int", local.pixelWidth )
  • ) />
  •  
  • <!---
  • NOTE: Editing the buffered image has a direct
  • affect on the image from which we retrieved the
  • buffered image. It is passed by REFERENCE!
  •  
  • As such, we don't need to explicitly move the
  • updated buffered image data "back" into the
  • image for the changes to take place.
  • --->
  •  
  • </cfloop>
  •  
  • </cfloop>
  •  
  • <!---
  • Return out. No need to return the original image since
  • it is be altered directly.... I don't really care for this
  • strategy, but it keeps in line with the exisitng image
  • functions provided by ColdFusion.
  • --->
  • <cfreturn />
  • </cffunction>

As you can see in the code above, I am getting a reference to the ColdFusion image's underlying BufferedImage object. This buffered image (pixel data) is passed by reference. This means that we are not working with a copy of it; as such, any changes that we make to the buffered image data will immediately affect the ColdFusion image from which we got it. That is why, in the above function, I never have to set the altered buffered image data back into the ColdFusion image object.

Once I had the imagePixelate() function in place, I wrote some test code to see how this would affect images at different "pixel" sizes:

  • <!--- Read in the image. --->
  • <cfimage
  • name="girl"
  • action="read"
  • source="./girl2.jpg"
  • />
  •  
  • <cfoutput>
  •  
  • <div align="center">
  •  
  • <h3>
  • Original Image
  • </h3>
  •  
  • <p>
  • <cfimage
  • action="writetobrowser"
  • source="#girl#"
  • />
  • </p>
  •  
  •  
  • <!---
  • Now, let's loop over a few different pixel sizes and
  • apply each to the *original* image.
  • --->
  • <cfloop
  • index="pixelSize"
  • from="5"
  • to="30"
  • step="5">
  •  
  • <!---
  • Copy the original image since the pixelation is
  • applied directly to the image passed-in.
  • --->
  • <cfset testGirl = imageCopy(
  • girl,
  • 0,
  • 0,
  • imageGetWidth( girl ),
  • imageGetHeight( girl )
  • ) />
  •  
  • <!---
  • Pixelate the image using the given pixel size of
  • this iteration.
  • --->
  • <cfset imagePixelate( testGirl, pixelSize ) />
  •  
  • <!--- Output the image. --->
  • <h3>
  • Pixel Size: #pixelSize#
  • </h3>
  •  
  • <p>
  • <cfimage
  • action="writetobrowser"
  • source="#testGirl#"
  • />
  • </p>
  •  
  • </cfloop>
  •  
  • </div>
  •  
  • </cfoutput>

As you can see in this code, we are iterating over an image, applying the effect with incrementing pixel dimensions. Notice that before I apply the pixelation effect, I have to create a copy of the image. This is due to the fact that effect works directly on the original image (via its buffered image data); I have to copy the image to make sure the subsequent effects are not all applied to the same variable.

When we run the above code, we get the following page output:

 
 
 
 
 
 
Pixelate A ColdFusion Image Using The Underlying Buffered Image Data. 
 
 
 

Anyway, I just think that's a cool little effect.

Tweet This Great article by @BenNadel - Pixelating An Image With ColdFusion Thanks my man — you rock the party that rocks the body!



Reader Comments

@Raymond,

Ah, good point!

@James,

This was built in CF8. I can't remember off-hand when images were introduced. I think they were in ColdFusion starting in CF8.

@Rick,

It might create something similar, but I don't think you would have any control over the size of the resultant pixels... or at least it might take a different set of calculations (ie. smaller size to resize to before re-enlarging).

I have done the same via actionscript and used to scale the 20*20 area to 1*1 and it merged the colors automatically, so you would just have to get the color of the scaled pixel.
Also, not sure it would be faster ...

@Philippe,

Oh that's an interesting idea; and I think it kind of goes nicely with what @Rick was talking about - just at the smaller scale.

To me this is Spanish and Chinese together at the same time. Right now Im trying to learn PHP which is enough for me so far :)

But thanks anyway, maybe in a few months I will understand that.

Tested and verified:

<cfset var origWidth = image.width>
<cfset var origHeight = image.height>
<cfset imageResize(arguments.image, arguments.image.width / arguments.pixelSize, "")>
<cfset imageResize(arguments.image, origWidth, origHeight, "nearest")>

By specifying the interpolation as "nearest", you disable any of the smarter interpolation that would lead to a blurry image.

(Not that your post isn't useful in its own right -- I'm just nit-picking.)

@Rick,

Very cool! I just tested this to see for myself and it worked perfectly. I guess this goes to show you how important it can be to understand the differences between all the various interpolation methods (is that the word I'm looking for)?