Created
August 7, 2012 14:23
-
-
Save bennadel/3285784 to your computer and use it in GitHub Desktop.
Mapping CSS Sprite Image Coordinates With ColdFusion
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- Param the form values. ---> | |
<cfparam name="form.submitted" type="boolean" default="false" /> | |
<cfparam name="form.sprite" type="string" default="" /> | |
<!--- ----------------------------------------------------- ---> | |
<!--- ----------------------------------------------------- ---> | |
<!--- Check to see if the form has been submitted. ---> | |
<cfif ( | |
form.submitted && | |
len( form.sprite ) | |
)> | |
<!--- Upload the file. ---> | |
<cffile | |
result="upload" | |
action="upload" | |
filefield="sprite" | |
destination="#expandPath( './uploads/' )#" | |
nameconflict="makeunique" | |
/> | |
<!--- Map the spirte elements within the uploading file. ---> | |
<cfset spriteMapper = new SpriteMapper( | |
imageNew( "#upload.serverDirectory#/#upload.serverFile#" ) | |
) /> | |
</cfif> | |
<!--- ----------------------------------------------------- ---> | |
<!--- ----------------------------------------------------- ---> | |
<cfcontent type="text/html; charset=utf-8" /> | |
<cfoutput> | |
<!doctype html> | |
<html> | |
<head> | |
<title>CSS Sprite Coordinates</title> | |
</head> | |
<body> | |
<h1> | |
CSS Sprite Coordinates | |
</h1> | |
<form | |
method="post" | |
action="#cgi.script_name#" | |
enctype="multipart/form-data"> | |
<input type="hidden" name="submitted" value="true" /> | |
<p> | |
Upload your CSS sprite PNG:<br /> | |
<input type="file" name="sprite" size="50" /> | |
</p> | |
<p> | |
<input type="submit" value="Upload" /> | |
</p> | |
</form> | |
<!--- | |
Check to see if we have our sprite mapper. If so, then | |
we can access the shapes and output the coordinates. | |
---> | |
<cfif !isNull( spriteMapper )> | |
<h2> | |
Sprite Results: | |
</h2> | |
<!--- | |
To make the output easier to read, we're going to | |
create classes rather than inline styles. As such, | |
we'll need an index counter to make the class | |
names unique. | |
---> | |
<cfset classIndex = 0 /> | |
<!--- Output each shape. ---> | |
<cfloop | |
index="shape" | |
array="#spriteMapper.getShapes()#"> | |
<cfset classIndex++ /> | |
<!--- Get the dimensions and offset of the sprite. ---> | |
<cfset box = shape.getBoundingBox() /> | |
<style type="text/css"> | |
##sprite#classIndex# { | |
background-image: url( "./uploads/#upload.serverFile#" ) ; | |
background-position: -#box.minX#px -#box.minY#px ; | |
background-repeat: no-repeat ; | |
border: 5px solid ##D0D0D0 ; | |
height: #box.height#px ; | |
margin-bottom: 30px ; | |
width: #box.width#px ; | |
} | |
</style> | |
<div id="sprite#classIndex#"> | |
<br /> | |
</div> | |
</cfloop> | |
</cfif> | |
</body> | |
</html> | |
</cfoutput> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<cfcomponent output="false"> | |
<cfscript> | |
// I map the incoming opaque pixels to shapes within the sprite | |
// image. | |
function init(){ | |
// I am the collection of known, valid shapes. As shapes | |
// evolve in the map, some may be absorbed by others and will | |
// be removed from this collection. | |
variables.shapes = {}; | |
// Each shape is keyed by its own unique ID. | |
variables.shapeIDIndex = 0; | |
// Return this object reference. | |
return( this ); | |
}; | |
// I add the given shape pixel to the map. When a pixel is added, | |
// it is either consumed by an existing shape, or is the means to | |
// start a completely new shape. | |
function addPixel( Numeric x, Numeric y ){ | |
// Check the surrounding pixels for known shapes. | |
var surroundingShapes = this._getSurroundingShapes( x, y ); | |
// Check to see if we have any surrounding shapes. | |
if (arrayLen( surroundingShapes )){ | |
// Add the current pixel to the first known shape. | |
surroundingShapes[ 1 ].addPixel( x, y ); | |
// If this new pixel has bridged the gap between two | |
// known shapes, when this pixel has united the two | |
// shapes into one shape. As such, one of the surrounding | |
// shapes must absorb the other ones. | |
this._mergeShapes( surroundingShapes ); | |
// Nothing mroe to do with this pixel. | |
return; | |
} | |
// If we made it this far then we have a completely new shape. | |
// Create the shape with a new, unique ID. | |
var shape = new SpriteShape( | |
++variables.shapeIDIndex, | |
x, | |
y | |
); | |
// Store the new shape. | |
variables.shapes[ shape.getID() ] = shape; | |
} | |
// I get the shape that contains the given pixel. | |
function _getShapeContainingPixel( Numeric x, Numeric y ){ | |
// Short-circuit some out-of-bounds cases. | |
if ( | |
(x < 0) || | |
(y < 0) | |
){ | |
// These are not valid coordinates. | |
return; | |
} | |
// Loop over the known shapes to if any of them contain a | |
// pixel with the given coordinates. | |
for (var shapeID in variables.shapes){ | |
var shape = variables.shapes[ shapeID ]; | |
if (shape.containsPixel( x, y )){ | |
return( shape ); | |
} | |
} | |
// If we made it this far, no shape was found. | |
return; | |
} | |
// I return the unique set of shapes found in the sprite map. | |
function getShapes(){ | |
var shapes = []; | |
// Convert the key-based collection into an indexed collection. | |
for (var shapeID in variables.shapes){ | |
arrayAppend( | |
shapes, | |
variables.shapes[ shapeID ] | |
); | |
} | |
// Return the shapes. | |
return( shapes ); | |
} | |
// I get the shapes in the pixels surrounding the given coordinate. | |
function _getSurroundingShapes( Numeric x, Numeric y ){ | |
// Define the four potentionally known coorindates in relation | |
// to the given pixel. The other direcitons (east and south) | |
// have not yet been explored. | |
// | |
// NOTE: This direcitonal assumption is based on the logic | |
// contained within the scanning alogrithm of the SpriteMapper | |
// component. As such, this part of the code is strongly | |
// coupled to the other component. | |
var pixels = [ | |
{ | |
x = (x - 1), | |
y = y | |
}, | |
{ | |
x = (x - 1), | |
y = (y - 1) | |
}, | |
{ | |
x = x, | |
y = (y - 1) | |
}, | |
{ | |
x = (x + 1), | |
y = (y - 1) | |
} | |
]; | |
// Define the shapes that surround the given pixel. | |
var shapes = []; | |
// Keep track of the IDs of the shapes we found, so that we | |
// keep the collection of returned shapes unique. | |
var shapeIDs = {}; | |
// For a small optimization, we know that if we find a shape | |
// in the first pixel, we can skip the check for the middle | |
// two pixels and jump to the last pixel. | |
var skipMiddlePixels = false; | |
// Loop over connected pixels to gather regions. | |
for (var i = 1 ; i <= 4 ; i++){ | |
if (skipMiddlePixels && (i < 4)){ | |
continue; | |
} | |
var pixel = pixels[ i ]; | |
// Get the parent shape (if there is one). | |
var shape = this._getShapeContainingPixel( pixel.x, pixel.y ); | |
// If we found a shape at the given coordinates, | |
// add it to the collection. | |
if ( | |
!isNull( shape ) && | |
!structKeyExists( shapeIDs, shape.getID() ) | |
){ | |
// Add this shape to the collection. | |
arrayAppend( shapes, shape ); | |
// Cache the ID. | |
shapeIDs[ shape.getID() ] = true; | |
// If this is the first pixel, then skip directly | |
// to the last pixel. | |
if (i == 1){ | |
skipMiddlePixels = true; | |
} | |
} | |
} | |
// Return the surrounding shapes. | |
return( shapes ); | |
} | |
// I merge the given, connected shapes. | |
function _mergeShapes( shapes ){ | |
// Get the number of shapes to merge. | |
var shapeCount = arrayLen( shapes ); | |
// If there is only one shape, there's nothing to merge. | |
if (shapeCount == 1){ | |
return; | |
} | |
// If there is more than one shape, all the secondary shapes | |
// will get merge down into the first shape. Get a reference | |
// to the first shape. | |
var firstShape = shapes[ 1 ]; | |
// Merge all the remaining shapes into the first shape. | |
for (var i = 2 ; i <= shapeCount ; i++){ | |
var oldShape = shapes[ i ]; | |
// Remove this shape from the cache. | |
structDelete( variables.shapes, oldShape.getID() ); | |
// Merge this shape into the first one. | |
firstShape.absorbShape( oldShape ); | |
} | |
} | |
</cfscript> | |
</cfcomponent> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<cfcomponent output="false"> | |
<cfscript> | |
// I handle the reading of the image's underlying pixel data, | |
// determining which pixels are part of the canvas (ie. which | |
// are transparent) and which pixels are part of a shape (ie. | |
// which are opaque). | |
function init( Any image ){ | |
// Get the underlying buffered image so that we can gain | |
// access to the pixel data. | |
variables.bufferedImage = imageGetBufferedImage( image ); | |
// Store our actual map (this takes care of turning opaque | |
// pixels into shapes). | |
variables.spriteMap = new SpriteMap(); | |
// Find all the pixels in the image that may be part of shapes. | |
this._findShapePixels(); | |
// Return this object reference. | |
return( this ); | |
} | |
// I traverse the image, looking for pixels that will be part of | |
// shapes (ie. for anything that is NOT transparent). | |
function _findShapePixels(){ | |
// Get the bounding coordinates for our graphic. | |
var width = variables.bufferedImage.getWidth(); | |
var height = variables.bufferedImage.getHeight(); | |
// Move down the image. | |
for (var y = 0 ; y < height ; y++ ){ | |
// For each row, move across, looking at each pixel. | |
for (var x = 0 ; x < width ; x++){ | |
// Examine the given pixel to see if it should be | |
// used or ignored. | |
this._processPixel( x, y ); | |
} | |
} | |
} | |
// I return the mapped shapes. | |
function getShapes(){ | |
return( variables.spriteMap.getShapes() ); | |
} | |
// I determine if the given pixel is transparent. | |
function _pixelIsTransparent( Numeric x, Numeric y ){ | |
// Get the numeric representation of the pixel in the image. | |
var pixel = variables.bufferedImage.getRGB( | |
javaCast( "int", x ), | |
javaCast( "int", y ) | |
); | |
// Get the left-most byte of the pixel (which holds the 8-bit | |
// alpha component of the pixel integer). | |
var alphaChannel = bitSHRN( pixel, 24 ); | |
// Consider this pixel transparent if it has full transparency. | |
return( alphaChannel eq 0 ); | |
} | |
// I look at a given pixel and determine if it is blank | |
// (transparent) or, if it should be consumed as part of a | |
// sprite shape. | |
function _processPixel( Numeric x, Numeric y ){ | |
// Check to see if this pixel is transparent. If it is | |
// transparent, then it is not part of a shape. | |
if (this._pixelIsTransparent( x, y )){ | |
// Nothing more to do with this pixel. | |
return; | |
} | |
// Add this pixel to the map. | |
variables.spriteMap.addPixel( x, y ); | |
} | |
</cfscript> | |
</cfcomponent> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<cfcomponent output="false"> | |
<cfscript> | |
// I manage the pixels for a single shape within the sprite. As | |
// pixels are added, they are mapped and the bouding box of the | |
// shape is updated. | |
function init( Numeric id, Numeric x, Numeric y ){ | |
// Store the unique ID of the shape. | |
variables.id = id; | |
// Store the pixel map, including our first pixel. | |
variables.pixelMap = { | |
"#x#:#y#" = true | |
}; | |
// Set up the initial bounding box defined by the first pixel. | |
variables.minX = x; | |
variables.maxX = (x + 1); | |
variables.minY = y; | |
variables.maxY = (y + 1); | |
// Return this object reference. | |
return( this ); | |
} | |
// I absorb the dimentions of the given shape. | |
function absorbShape( Any shape ){ | |
// Absorb all of the pixels in the underlying map. | |
structAppend( | |
variables.pixelMap, | |
shape.getPixelMap() | |
); | |
// Get the other shape's boudning box. | |
var boundingBox = shape.getBoundingBox(); | |
// Grow the current bounding box to encompass the new | |
// bounding box of the absorbed shape. | |
variables.minX = min( variables.minX, boundingBox.minX ); | |
variables.maxX = max( variables.maxX, boundingBox.maxX ); | |
variables.minY = min( variables.minY, boundingBox.minY ); | |
variables.maxY = max( variables.maxY, boundingBox.maxY ); | |
} | |
// I add the given pixel to the shape. | |
function addPixel( Numeric x, Numeric y ){ | |
// Add it the map. | |
variables.pixelMap[ "#x#:#y#" ] = true; | |
// Adjust the min/max box coordinates. | |
variables.minX = min( variables.minX, x ); | |
variables.maxX = max( variables.maxX, (x + 1) ); | |
variables.minY = min( variables.minY, y ); | |
variables.maxY = max( variables.maxY, (y + 1) ); | |
} | |
// I determine if the given pixel is within the known pixels of | |
// mapped shape. | |
function containsPixel( Numeric x, Numeric y ){ | |
return( | |
structKeyExists( variables.pixelMap, "#x#:#y#" ) | |
); | |
} | |
// I get the bounding box for the shape based on the min/max | |
// coordinates of the bounding box. | |
function getBoundingBox(){ | |
var boundingBox = { | |
minX = variables.minX, | |
maxX = variables.maxX, | |
minY = variables.minY, | |
maxY = variables.maxY, | |
width = (variables.maxX - variables.minX), | |
height = (variables.maxY - variables.minY) | |
}; | |
return( boundingBox ); | |
} | |
// I return the ID property. | |
function getID(){ | |
return( variables.id ); | |
} | |
// I return the underlying pixel map. | |
function getPixelMap(){ | |
return( variables.pixelMap ); | |
} | |
</cfscript> | |
</cfcomponent> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment