Image Trim with ColdFusion

I was working on QR barcode using Zxing and the barcode generated by Zxing tended to give extra white spaces on some barcodes, but my designer wanted a trimmed image without any extra white space around it. ColdFusion does not have an image trim function, so I put together a small function to trim the background of an image.

This function takes two arguments.
Image (required)
This can take a ColdFusion image object or path to an image file. If you provide an image object, the function will return a trimmed image object; if you provide a path to an image file, the function will overwrite that image with the newly-trimmed smaller image.
bgcolor (default:white)
This argument takes the background color of the image. You can pass hex color, or (R,G,B) values. Also, you can a pass the position of the image for the function to pick a background color form. Values are 'top-left' 'top-right' 'bottom-left' 'bottom-right' or (x,y) positions.

This function is not handy at trimming scanned images, since they do not have a seamless background. To trim scanned images either we have to apply a “reduce moiré” filter on the source image or look for more than one background color. At this time however, this is only for images generated by our own coding that always have a seamless background color. 

   1: <cffunction name="trimimage" output="no" description="Trim Images - only clearn space get trimed. ">
   2:  <cfargument name="image" required="yes" hint="image object or file path to image">
   3:  <cfargument name="bgcolor" required="no" default="ffffff" hint="(r,b,g) values or hex color value or postion of the image to pick color from - values are ['top-left' 'top-right' 'bottom-left' 'bottom-right' or (x,y)]">
   4: 
   5: 
   6:  <cfif not IsImage(arguments.image) and FileExists(arguments.image)>
   7:  <!--- **************************************************** --->
   8:  <!--- if image path given, read it & save it at the end --->
   9:  <!--- **************************************************** --->
  10:  <cfset local.imagepath = arguments.image>
  11:  <cfset arguments.image = ImageRead(arguments.image)>
  12:  </cfif>
  13: <!--- **************************************************** --->
  14:  <!--- get image ready --->
  15:  <!--- **************************************************** --->
  16:  <cfswitch expression="#ImageInfo(arguments.image).colormodel.colormodel_type#">
  17:  <cfcase value="IndexColorModel">
  18:  <!--- remove alpha from 8bit images --->
  19:  <cfset local.newimage = ImageNew("",arguments.image.getWidth(), arguments.image.getHeight(),'RGB','FFFFFF')>
  20:  <cfset ImagePaste(local.newimage, arguments.image, 0, 0)>
  21:  <cfset arguments.image = local.newimage>
  22:  <cfset StructDelete(local,'newimage')>
  23:  </cfcase>
  24:  </cfswitch>
  25:  <cfset local.iw = arguments.image.getWidth()-1>
  26:  <cfset local.ih = arguments.image.getHeight()-1>
  27:  <cfset local.bufimg = ImageGetBufferedImage(arguments.image)>
  28: <!--- **************************************************** --->
  29:  <!--- get the background color values --->
  30:  <!--- **************************************************** --->
  31:  <cfswitch expression="#arguments.bgcolor#">
  32:  <cfcase value="top-left">
  33:  <cfset local.bgcolor = local.bufimg.getdata().GetPixel( JavaCast( "int", 0 ),JavaCast( "int", 0 ), JavaCast( "int[]", ListToArray( "0,0,0,0" ) ) )>
  34:  <cfset local.bgcolor = '#local.bgcolor[1]##local.bgcolor[2]##local.bgcolor[3]#'>
  35:  </cfcase>
  36:  <cfcase value="top-right">
  37:  <cfset local.bgcolor = local.bufimg.getdata().GetPixel( JavaCast( "int", local.iw ),JavaCast( "int", 0 ), JavaCast( "int[]", ListToArray( "0,0,0,0" ) ) )>
  38:  <cfset local.bgcolor = '#local.bgcolor[1]##local.bgcolor[2]##local.bgcolor[3]#'>
  39:  </cfcase>
  40:  <cfcase value="bottom-left">
  41:  <cfset local.bgcolor = local.bufimg.getdata().GetPixel( JavaCast( "int", 0 ),JavaCast( "int", local.ih ), JavaCast( "int[]", ListToArray( "0,0,0,0" ) ) )>
  42:  <cfset local.bgcolor = '#local.bgcolor[1]##local.bgcolor[2]##local.bgcolor[3]#'>
  43:  </cfcase>
  44:  <cfcase value="bottom-right">
  45:  <cfset local.bgcolor = local.bufimg.getdata().GetPixel( JavaCast( "int", local.iw ),JavaCast( "int", local.ih ), JavaCast( "int[]", ListToArray( "0,0,0,0" ) ) )>
  46:  <cfset local.bgcolor = '#local.bgcolor[1]##local.bgcolor[2]##local.bgcolor[3]#'>
  47:  </cfcase>
  48:  <cfdefaultcase>
  49:  <cfswitch expression="#listlen(arguments.bgcolor)#">
  50:  <cfcase value="3">
  51:  <!--- rbg background value --->
  52:  <cfset local.bgcolor = ListToArray(arguments.bgcolor)>
  53:  <cfset local.bgcolor = '#val(local.bgcolor[1])##val(local.bgcolor[2])##val(local.bgcolor[3])#'>
  54:  </cfcase>
  55:  <cfcase value="2">
  56:  <!--- x,y cordination --->
  57:  <cfset local.bgcolor = local.bufimg.getdata().GetPixel( JavaCast( "int", val(listfirst(arguments.bgcolor)-1) ),JavaCast( "int", val(listlast(arguments.bgcolor))-1 ), JavaCast( "int[]", ListToArray( "0,0,0,0" ) ) )>
  58:  <cfset local.bgcolor = '#local.bgcolor[1]##local.bgcolor[2]##local.bgcolor[3]#'>
  59:  </cfcase>
  60:  <cfdefaultcase>
  61:  <!--- hex value --->
  62:  <cfset local.bgcolor = InputBaseN( replace(arguments.bgcolor,'##',"") , "16" )>
  63:  <cfset local.bgcolor = "#val(BitSHRN(BitAnd( local.bgcolor, InputBaseN( "FF0000", 16 ) ),16))##val(BitSHRN(BitAnd( local.bgcolor, InputBaseN( "00FF00", 16 ) ),8))##val(BitAnd( local.bgcolor, InputBaseN( "0000FF", 16 ) ))#">
  64:  </cfdefaultcase>
  65:  </cfswitch>
  66:  </cfdefaultcase>
  67:  </cfswitch>
  68: 
  69:  <!--- **************************************************** --->
  70:  <!--- find positions --->
  71:  <!--- **************************************************** --->
  72:  <cfset local.border = StructNew()>
  73:  <!--- find top border --->
  74:  <cfloop from="0" to="#local.ih#" index="local.h">
  75:  <cfloop from="0" to="#local.iw#" index="local.w">
  76:  <cfset local.thiscolor = local.bufimg.getRaster().GetPixel( JavaCast("int",local.w),JavaCast("int",local.h), JavaCast( "int[]", ListToArray( "0,0,0,0" ) ) )>
  77:  <cfset local.thiscolor = '#local.thiscolor[1]##local.thiscolor[2]##local.thiscolor[3]#'>
  78:  <cfif local.thiscolor neq local.bgcolor>
  79:  <cfset local.border.top = local.h>
  80:  <cfbreak>
  81:  </cfif>
  82:  </cfloop>
  83:  <cfif StructKeyExists(local.border,'top')>
  84:  <cfbreak>
  85:  </cfif>
  86:  </cfloop>
  87:  <!--- find the left border --->
  88:  <cfif StructKeyExists(local.border,'top')>
  89:  <cfloop from="0" to="#local.iw#" index="local.w">
  90:  <cfloop from="0" to="#local.ih#" index="local.h">
  91:  <cfset local.thiscolor = local.bufimg.getRaster().GetPixel( JavaCast("int",local.w),JavaCast("int",local.h), JavaCast( "int[]", ListToArray( "0,0,0,0" ) ) )>
  92:  <cfset local.thiscolor = '#local.thiscolor[1]##local.thiscolor[2]##local.thiscolor[3]#'>
  93:  <cfif local.thiscolor neq local.bgcolor>
  94:  <cfset local.border.left = local.w>
  95:  <cfbreak>
  96:  </cfif>
  97:  </cfloop>
  98:  <cfif StructKeyExists(local.border,'left')>
  99:  <cfbreak>
 100:  </cfif>
 101:  </cfloop>
 102:  </cfif>
 103:  <!--- find the bottom border --->
 104:  <cfif StructKeyExists(local.border,'left')>
 105:  <cfloop from="#local.ih#" to="0" index="local.h" step="-1">
 106:  <cfloop from="0" to="#local.iw#" index="local.w">
 107:  <cfset local.thiscolor = local.bufimg.getRaster().GetPixel( JavaCast("int",local.w),JavaCast("int",local.h), JavaCast( "int[]", ListToArray( "0,0,0,0" ) ) )>
 108:  <cfset local.thiscolor = '#local.thiscolor[1]##local.thiscolor[2]##local.thiscolor[3]#'>
 109:  <cfif local.thiscolor neq local.bgcolor>
 110:  <cfset local.border.bottom = (local.h+1)-local.border.top>
 111:  <cfbreak>
 112:  </cfif>
 113:  </cfloop>
 114:  <cfif StructKeyExists(local.border,'bottom')>
 115:  <cfbreak>
 116:  </cfif>
 117:  </cfloop>
 118:  </cfif>
 119:  <!--- find the right border --->
 120:  <cfif StructKeyExists(local.border,'bottom')>
 121:  <cfloop from="#local.iw#" to="0" index="local.w" step="-1">
 122:  <cfloop from="0" to="#local.ih#" index="local.h">
 123:  <cfset local.thiscolor = local.bufimg.getRaster().GetPixel( JavaCast("int",local.w),JavaCast("int",local.h), JavaCast( "int[]", ListToArray( "0,0,0,0" ) ) )>
 124:  <cfset local.thiscolor = '#local.thiscolor[1]##local.thiscolor[2]##local.thiscolor[3]#'>
 125:  <cfif local.thiscolor neq local.bgcolor>
 126:  <cfset local.border.right = (local.w+1)-local.border.left>
 127:  <cfbreak>
 128:  </cfif>
 129:  </cfloop>
 130:  <cfif StructKeyExists(local.border,'right')>
 131:  <cfbreak>
 132:  </cfif>
 133:  </cfloop>
 134:  </cfif>
 135: <cfif StructKeyExists(local.border,'right')>
 136: <span><cfset ImageCrop(arguments.image, local.border.left, local.border.top, local.border.right, local.border.bottom)></span>
 137:  </cfif>
 138: 
 139:  <!--- **************************************************** --->
 140:  <!--- image path given, save the image --->
 141:  <!--- **************************************************** --->
 142:  <cfif StructKeyExists(local,'imagepath')>
 143:  <cfimage overwrite="yes" action="write" source="#arguments.image#" format="#listlast(local.imagepath,'.')#" destination="#local.imagepath#">
 144:  <cfreturn local.imagepath>
 145:  <cfelse>
 146:  <cfreturn arguments.image>
 147:  </cfif>
 148: </cffunction>
Show/Hide Line Numbers . Full Screen . Plain

2 Comments :
Rob
Thursday 13 March 2014 12:40 PM
there is an error in the code
<cfset ImageCrop(arguments.image, local.border.top, local.border.left, local.border.right, local.border.bottom)>
should be:
<cfset ImageCrop(arguments.image, local.border.left, local.border.top, local.border.right, local.border.bottom)>

ImageCrop takes x,y,width,height
Thursday 13 March 2014 01:08 PM
Thanks Rob. I updated the code above.