I've been trying to add text watermark on images using imageDrawText tag in CF10.
here is some testing code
<cfset img = imageNew("",500,500,"rgb","blue")>
<cfset text = "This is just another test! See if text fits the imgage...">
<cfset buffered = ImageGetBufferedImage(img)>
<cfset context = buffered.getGraphics().getFontRenderContext()>
<cfset Font = createObject("java", "java.awt.Font")>
<cfset textFont = Font.init( "Arial", Font.BOLD, javacast("int", 40))>
<cfset textLayout = createObject("java", "java.awt.font.TextLayout").init( text, textFont, context)>
<cfset textBounds = textLayout.getBounds()>
<cfset textWidth = textBounds.getWidth()>
<cfset textHeight = textBounds.getHeight()>
<cfset attr = { font="Arial", size="40", style="bold" }>
<cfset x = (ImageGetWidth(img) / 2 - textWidth / 2)>
<cfset y = (ImageGetHeight(img) / 2 + textHeight / 2)>
<cfset imageSetDrawingColor(img,"black")>
<cfset imageDrawText(img,text, x, y, attr)>
<cfimage action="writeToBrowser" source="#img#">
the problem is that I do not know how to break lines and center the text...
on the left is what the code produces and on the right is what I would like to get
It is important to note that the font size and the number of characters will vary from one image to another and that's the main reason I do not know how to count it correctly.
My initial idea was to count characters and see how many will fit in image width though that is not possible due to the above reasons. So is there a one-liner for this or some sort of mathematical function is needed to manually split text width and break lines? also maybe I should use additional or x imageDrawText tags to display each line separately, though still need to split it somehow!
Wrapping text is definitely not a one-liner ;-) Unfortunately, you cannot simply count characters, because the sizes of the individual glyphs can vary, unless maybe you are using a mono-spaced font.
Essentially, the only way to accurately wrap text is to iterate through the string, measure the size of each word (in the current font) and see if it fits on the current line. If not, start a new line. There is a little more to it, but bottom line it involves two steps: 1) measure and split the text into lines 2) then draw the lines of text onto the image
There are different ways you could handle measuring and splitting the text into lines. My preference is using a LineBreakMeasurer because it does most of the hard work. You just give it the width of the wrapping area and it auto-magically calculates how much of the text can fit on the current line.
Many moons ago, I put together a small library for wrapping and scaling image text (old blog, not maintained). Give it a whirl. It is a little old, but I think it should do the trick.
UPDATE:
I just remembered the library does not center the text vertically, only horizontally. However, since it returns the dimensions of the wrapped text, you could easily calculate it. Here is a very quick and dirty example:
Code:
<cfset text = "If you're going through hell, keep going" />
<!--- overall image dimensions --->
<cfset imageWidth = 500 />
<cfset imageHeight = 500 />
<!--- desired wrapping area --->
<cfset textMargin = 25 />
<cfset wrapWidth = imageWidth - (textMargin*2) />
<cfset wrapHeight = 100 />
<!--- create a blank image with background --->
<cfset img = ImageNew("", imageWidth, imageHeight, "rgb") />
<cfset imageSetDrawingColor( img, "d9d9ff" ) />
<cfset imageDrawRect(img, 0, 0, imageWidth, imageHeight, true) />
<!--- measure dimensions of wrapped text --->
<cfset util = createObject("java", "org.cfsearching.image.WrapImageText") />
<cfset util.init( text, wrapWidth, wrapHeight ) />
<cfset util.setAlignment( util.CENTER_ALIGN ) />
<cfset util.setColor( "0000ff" ) />
<cfset util.setDrawText( false ) />
<cfset util.setX( textMargin ) />
<cfset util.setFont( "Arial", util.BOLD, 40) />
<!--- note: when disabled, text may overflow established wrap height
<cfset util.setAutoScale( false ) />
--->
<!--- use dimensions to center text VERTICALLY --->
<cfset dimen = util.wrapText( ImageGetBufferedImage(img) ) />
<cfset y = (imageHeight - dimen.height) / 2 />
<cfset util.setY( y ) />
<!--- draw the wrapped text --->
<cfset util.setDrawText( true ) />
<cfset dimen = util.wrapText( ImageGetBufferedImage(img) ) />
<!--- display results --->
<cfimage action="writeToBrowser" source="#img#" /> <br />
Result: (Using default font/auto scaling)
Related
I'm writing a simple web-based game that requires me to create random overworld 'zones' consisting of a few thousand terrain tiles (probably between 100x100 and 500x500). Most advice online suggests that I start by generating perlin noise and using that as an altitude map, followed by another instance for moisture, another for temperature, and so on, and then assign terrain values based on a combination of those.
I would prefer not to rely on installing any other languages or programs to do this. However, there doesn't appear to be any built-in function to generate a perlin noise map with CFML directly. What is the easiest way to do this with the minimum external dependencies?
Is there some "perlinNoise" java method I can make use of to build an array that I can then work with in CFML? Is there cfscript/cfml source code or a cfc available online to implement a perlin function (I don't know if I can translate something from another language myself)? Or would the easiest route be installing and using something like ImageMagick to generate/read an image file via cfexecute?
What I've Tried
I first attempted to convert the C++ code shown on wikipedia. This would probably be easy, if I had ever worked with C++ in my life. Unfortunately, I have not. I got as far as this:
<cffunction name="lerp" access="public" output="no" returntype="numeric" description="Function to linearly interpolate between a0 and a1">
<cfargument name="a0" type="numeric" required="yes">
<cfargument name="a1" type="numeric" required="yes">
<cfargument name="weight" type="numeric" required="yes">
<cfset returnVal = (1.0 - weight) * a0 + weight * a1>
<cfreturn returnVal>
</cffunction>
<cffunction name="dotGridGradient" access="public" output="no" returntype="numeric" description="Computes the dot product of the distance and gradient vectors.">
<cfargument name="ix" type="numeric" required="yes">
<cfargument name="iy" type="numeric" required="yes">
<cfargument name="x" type="numeric" required="yes">
<cfargument name="y" type="numeric" required="yes">
<!--- Precomputed (or otherwise) gradient vectors at each grid node --->
<!--- <cfset test = Gradient[IYMAX][IXMAX][2]> --->
<!--- Compute the distance vector --->
<cfset dx = x - ix>
<cfset dy = y - iy>
<!--- Compute the dot-product --->
<cfset returnVal= (dx*Gradient[iy][ix][0] + dy*Gradient[iy][ix][1])>
<cfreturn returnVal>
</cffunction>
<cffunction name="perlin" access="public" output="no" returntype="numeric" description="Compute Perlin noise at coordinates x, y">
<cfargument name="x" type="numeric" required="yes">
<cfargument name="y" type="numeric" required="yes">
<!--- Determine grid cell coordinates --->
<cfset x1 = int(x) + 1>
<cfset y1 = int(y) + 1>
<!--- Determine interpolation weights --->
<!--- Could also use higher order polynomial/s-curve here --->
<cfset sx = x - x0>
<cfset sy = y - y0>
<!--- Interpolate between grid point gradients --->
float n0, n1, ix0, ix1, value;
<cfset n0 = dotGridGradient(x0, y0, x, y)>
<cfset n1 = dotGridGradient(x1, y0, x, y)>
<cfset ix0 = lerp(n0, n1, sx)>
<cfset n0 = dotGridGradient(x0, y1, x, y)>
<cfset n1 = dotGridGradient(x1, y1, x, y)>
<cfset ix1 = lerp(n0, n1, sx)>
<cfset returnVal= lerp(ix0, ix1, sy)>
<cfreturn returnVal>
</cffunction>
However, only the lerp function actually runs. I had no idea what 'gradient' means. I assume it's a math function, but I'm not sure how to implement it here. My Google searches keep giving me different code for it, along with some explanations that aren't intuitive to me.
At this point using IM was getting far more appealing. It seems more powerful, and I was just avoiding it as figuring it out and having one more thing to install on every server move seemed like more work than having something all in code. With the code approach seeming more complicated than I anticipated, I took a break to try focusing on IM.
For that I started by creating a seeded plasma or fractal canvas, which worked great. I then tried many different methods to pull information for each pixel with limited success:
<cfexecute name="#ImageMagick#\magick.exe"
variable="imgResult"
timeout="60"
arguments="convert -size 500x500 -seed #seed# plasma:fractal -blur #blur# -shade 120x45 -auto-level #imgRoot#/temp/#fname#.png" />
<cfloop from="1" to="20" index="x">
<cfloop from="1" to="20" index="y">
<!--- <cfexecute name="#ImageMagick#\magick.exe"
variable="imgResult"
timeout="60"
arguments="convert '#imgRoot#/temp/#fname#.png[1x1+#x#+#y#]' #imgRoot#/temp/temp.png" /> --->
<!--- Works; takes 27s for 400 pixels. Will take hours for full size maps.
<cfexecute name="#ImageMagick#\magick.exe"
variable="imgResult"
timeout="60"
arguments="identify -verbose #imgRoot#/temp/#fname#.png[1x1+#x#+#y#]" />
<cfset imgResult = ListFirst(ListLast(imgResult, "gray("), "%")>
--->
<!--- Returns blank; probably because of u.r not being defined in a grayscale image?
<cfexecute name="#ImageMagick#\magick.exe"
variable="imgResult"
timeout="60"
arguments="convert #imgRoot#/temp/#fname#.png[1x1+#x#+#y#] -format ""%[fx:floor(255*u)]"" info" />
--->
<!--- Errors with some decode delegate error
<cfexecute name="#ImageMagick#\magick.exe"
variable="imgResult"
timeout="60"
arguments="convert #imgRoot#/temp/#fname#.png: -format '%[pixel:p{#x#,#y#}]' info" /> --->
<!--- Errors with some decode delegate error
<cfexecute name="#ImageMagick#\magick.exe"
variable="imgResult"
timeout="60"
arguments="convert #imgRoot#/temp/#fname#.png: -crop 1x1+#x#+#y# -depth 8 txt" />
--->
<!--- Returns the same value for every pixel
<cfexecute name="#ImageMagick#\magick.exe"
variable="imgResult"
timeout="60"
arguments="convert -verbose #imgRoot#/temp/#fname#.png[1x1+#x#+#y#] txt" />
--->
<cfexecute name="#ImageMagick#\magick.exe"
variable="imgResult"
timeout="60"
arguments="identify -verbose #imgRoot#/temp/#fname#.png[1x1+#x#+#y#]" />
<cfset imgResult = ListFirst(ListLast(imgResult, "gray("), "%")>
<cfset returnVal[x][y] = imgResult>
</cfloop>
</cfloop>
So my best method so far is requiring 27s to pull data for 400 pixels, and that's without doing anything with that data. If I should need to process a 160k pixel image (400x400) in a real world scenario, that works out to approximately 3 hours of pegging my processor. So assuming I need 3 maps (for altitude, humidity, and temperature), that's...impractical.
I've not been able to find a solution I'm completely happy with in terms of efficiency, but I've run out of time to solve this and move on. I will likely come back in the future to optimize, but for now, I have a solution that works, albeit slowly.
Per Mark Setchell's excellent answer at https://stackoverflow.com/a/26629083/762721, I've found that, surprisingly, the most efficient approach to my problem is to generate a fractal with Image Magic, use IM to write all of the color information out to a file, and then use Lucee to read that file in and parse each line for luminosity information. Here is the code I'm using:
<cfexecute name="#ImageMagick#\magick.exe"
variable="imgResult"
timeout="60"
arguments="convert -size 500x500 -seed #seed# plasma:fractal -blur #blur# -shade 120x45 -auto-level #imgRoot#/temp/#fname#.png" />
<cfexecute name="#ImageMagick#\magick.exe"
variable="imgResult"
timeout="60"
arguments="convert #imgRoot#/temp/#fname#.png -depth 8 #imgRoot#/temp/test.txt" />
<cfset myfile = FileOpen("#imgRoot#/temp/test.txt", "read")>
<cfloop condition="NOT FileisEOF(myfile)">
<cfset thisLine = FileReadLine(myfile)>
<cfset x = listFirst(thisLine, ",")>
<cfset y = listGetAt(thisLine, 2, ",")>
<cfset y = listFirst(y, ":")>
<cfif isNumeric(x) and isNumeric(y)>
<cfset thisStart = FindNoCase("gray(", thisLine)>
<cfif thisStart is not 0>
<cfset thisVal = Mid(thisLine, thisStart+5, 99999)>
<cfset thisVal = listFirst(thisVal, ")")>
<cfset returnVal[x+1][y+1] = "#thisVal#">
</cfif>
</cfif>
</cfloop>
<cfset FileClose(myfile)>
I was able to run this on a 250k pixel image (500x500) in 7.1 minutes, making it almost 40 times faster than my attempt to get the pixel information directly. I think there's lots of room for both optimization, and validation to avoid errors, and once I tighten it up a bit I'll come back and update this answer.
For now, using this to generate 3 500x500 images, parse the information, and write it to the database can be done in 30 minutes. Which while suboptimal, is practical.
I want to create database location records in mySQL. I have the following html string from a select box:
<cfset x='
<option value="1188">Aka Aka</option><option value="346">Ararimu</option><option value="293">Awhitu</option><option value="2851">Bombay</option><option value="865">Buckland</option>
'>
Rather than manually enter the records in the database, I'd like to strip out the html tags and end up with the following:
Aka Aka
Ararimu
Awhitu
Bombay
Buckland
Then I could do a simple loop based on line breaks and enter the data programatically. I can probably handle that part, but what I need to know is the simplest way to strip out the html to end up with the line break delimited list.
Here you go:
<cfset x='
<option value="1188">Aka Aka</option><option value="346">Ararimu</option><option value="293">Awhitu</option><option value="2851">Bombay</option><option value="865">Buckland</option>
'>
<cfset y = ListToArray(x, "</option>", "false", "true") />
<cfset z = ArrayNew(1) />
<cfloop array="#y#" index="name">
<cfif Trim(ListLast(name, ">")) is not "">
<cfset temp = ArrayAppend(z, ListLast(name, ">")) />
</cfif>
</cfloop>
<cfdump var="#z#" />
you have them in a 'z' array now, you can convert to list and add line break delimiters if you really want to.
The typical way government offices make you record mileage is to enter one number per input box like the example below:
So what I am trying to do is create one input box that can split the session variable into an array. Then when that session variable is split into an array I would like it to set each value in the array to its own session variable.
Mileage Input:
123456 -<cfoutput>#session.checkout.vehicle.mileage#</cfoutput>
array [1,2,3,4,5,6]
6 -<cfoutput>#session.checkout.vehicle.mileage1#</cfoutput>
5 -<cfoutput>#session.checkout.vehicle.mileage2#</cfoutput>
4 -<cfoutput>#session.checkout.vehicle.mileage3#</cfoutput>
3 -<cfoutput>#session.checkout.vehicle.mileage4#</cfoutput>
2 -<cfoutput>#session.checkout.vehicle.mileage5#</cfoutput>
1 -<cfoutput>#session.checkout.vehicle.mileage6#</cfoutput>
So then I will be able to prefill in an already created form that has the boxes split for only one per box.
Where I am super confused and trying to comprehend is that there will not always be 6 variables. Let's say the mileage is 2344. I am assuming it will need to know to start backwards, counting from the right to the left. That's why I started 6 at #session.checkout.vehicle.mileage1#
Hopefully I have not super confused anyone with what I am trying to do. And any help would be greatly appreciated!!
<cfparam name="form.mileage" default="#session.checkout.vehicle.mileage#">
...
<label for="mileage">Mileage:</label>
<input type="text" name="mileage"
id="mileage"
value="<cfoutput>#form.mileage#</cfoutput>">
Edit:
The issue I am having with this is let's say the mileage is 9000 all 0's will not show. (which is great for the first two zero's in (009000) but after the 9 those 0's would still need to show.) Do you any ideas for that issue? Or should this be a new question?
<cfset Mileage = "9000" />
<cfif mileage is not "Exempt">
<cfset Mileage = NumberFormat(trim(Mileage),"000000") />
<cfset MilArray = ReMatch("\d",Mileage) />
<cfelse>
<cfset MilArray = ["E","x","e","m","p","t"]>
</cfif>
<cfdump var="#MilArray#">
<cfif MilArray[1] is not "0">
<!---Section6 First box Odometer Reading--->
<cfpdfformparam name="E" value="#MilArray[1]#">
<cfelse>
<cfpdfformparam name="E" value="">
</cfif>
If I'm understanding, you want to divide the string into six easy to work with variables, or whatever the length of the variable is.
<cfset Mileage = "123456" />
<cfset MilArray = ReMatch("\d",Mileage) />
<cfdump var="#MilArray#" />
You can actually stick a Reverse() in there to reverse the string, this may be handy because you can have [1] at the ones place, [2] at tens, [3] at hundreds.
<cfset Mileage = "123456" />
<cfset MileageR = Reverse(Mileage) />
<cfset MilArray = ReMatch("\d",MileageR) />
<cfdump var="#MilArray#" />
\d by itself in regular expressions just means "one digit". It's the same as [0-9].
As the CFDUMP will show, ReMatch will split your mileage into an easy to work with array. If you use the reverse as above, you can say "The last digit of your mileage is #MilArray[1]#.", as an example.
Edit:
you know the \d ? is there a way to have it be either \d or only the word Exempt? is it possible to create both of those?
There are a few ways.
You can say
<cfif mileage is not "Exempt">
...
<cfelse>
<cfset MilArray = ["Exempt"]>
</cfif>
which creates a one dimensional array populated with "Exempt" as the only element, which might be useful later in your code so you know MilArray is always an array, or you can simply always work with the <cfif mileage is not "Exempt">.
A regex to accomplish the same thing is possible but it achieves the same as the above cfif, and you'd have to write exempt backwards if you're using reverse, like this
<cfset MilArray = ReMatchNoCase("\d|^Exempt$|^tpmexE$)",trim(Mileage)) />
<cfif MilArray[1] is "tpmexE"><cfset milArray = ["Exempt"] /></cfif>
Edit #2:
<cfif isDefined("session") and structKeyExists(session, 'checkout') and structKeyExists(session.checkout, 'info') and structKeyExists(session.checkout.info, 'oreading')>
<cfif isDefined("#MilArray[6]#") eq "">
<cfpdfformparam name="E" value="">
<!---Section6 First box Odometer Reading--->
<cfelse>
<cfpdfformparam name="E" value="#MilArray[6]#">
</cfif>
</cfif>
This is a task for ArrayIsDefined() (link)
<cfif isDefined("session") and structKeyExists(session, 'checkout') and structKeyExists(session.checkout, 'info') and structKeyExists(session.checkout.info, 'oreading')>
<cfset MilArray = ReMatch("\d",session.checkout.info.oreading) />
<cfif not ArrayIsDefined(MilArray,6)>
<cfpdfformparam name="E" value="">
<!---Section6 First box Odometer Reading--->
<cfelse>
<cfpdfformparam name="E" value="#MilArray[6]#">
</cfif>
.... I assume that it continues on down from here... <cfif not ArrayIsDefined(MilArray,5)>........</cfif>
</cfif>
Finally, while there's contention here on whether to use StructKeyExists() over IsDefined(), there's a narrow field where isDefined() fails.
(Don't put structures in the top level and in the variables scope. Cold Fusion gets confused--IE, don't create an object called "variables.form" or "variables.url"). Beyond that, It's mostly just semantics.
Anyway. once you have the above code working (because it's your code and your familiar with it), you might find it useful to switch to the easier to read IsDefined() version, because isDefined can check several levels deep in one condition.
<cfif isDefined("session.checkout.info.oreading')>
<cfset MilArray = ReMatch("\d",session.checkout.info.oreading) />
<cfif not ArrayIsDefined(MilArray,6)>
<cfpdfformparam name="E" value="">
<!---Section6 First box Odometer Reading--->
<cfelse>
<cfpdfformparam name="E" value="#MilArray[6]#">
</cfif>
</cfif>
Edit 3:
Leigh points out
Why so complicated? Can't you just left pad the value with spaces or zeroes? Then change the regex to check for either a digit or space? Then the array will always have six elements
This can be achieved like this:
<cfset Mileage = "exempt" />
<cfif mileage is not "Exempt">
<cfset Mileage = NumberFormat(trim(Mileage),"000000") />
<cfset MilArray = ReMatch("\d",Mileage) />
<cfelse>
<cfset MilArray = ["E","x","e","m","p","t"]>
</cfif>
<cfdump var="#MilArray#">
Which would conveniently drop Exempt into place (handy that it's 6 characters).
You need to do some prechecking before you start generating the pdf to make sure that mileage variable is Exempt or or numeric.
<cfif len((trim(mileage)) gt 6 or not ((isNumeric(trim(mileage))
or mileage is "exempt")>
<!--- The 6 above is a len-check, you may need to update that number to
something else later, but you'll have to put the same number of 0s
in the NumberFormat function.
If you change that number, and the 0s, you'll need to pad the
"Exempt array"... ie ["E","x","e","m","p","t"," "] --->
....raise a flag...
</cfif>
Here is a simpler way.
originalNumber = "123";
sixDigitNumber = right(("000000" & originalNumber), 6);
<cfoutput>
<cfloop from="1" to = "6" index="position">
do something useful with #Mid(sixDigitNumber, position, 1)#
</cfloop>
<cfoutput>
This is a strange one - I have 'virtually' identical code on 2 pages, it works on one but not the other. The incredibly unhelpful error message, "S3 Error Message." doesn't shed any light on where I'm going wrong with this.
The code below is identical on both pages - the only (very slight) difference between the 2 pages is the way imgLink is generated - on the working page it is obtained from a single source (an XML feed) and cfset, on the none-working page imgLink is initially set as 'none' and then a number of sources are checked until it finds one - it's still cfset in the same way, and I have a cfif to make sure it's valid before processing. HOWEVER - I have also tried hardcoding the source (i.e., pasting in the value that would usually be in the imgLink cfset) and it still fails.
I've debugged this in every way I can possibly think in the last day, without success. So, I guess I'm looking for pointers as to what else I should look at that could be causing it to fail.
The full error returned is -
An error occurred when performing a file operation create on file s3://mybucket/1577-67BC4EF7-1B21-866F-32E95DF67F3336C6-f.jpg.
The cause of this exception was: org.apache.commons.vfs.FileSystemException: Unknown message with code "S3 Error Message."..
And my code is;
<cfscript>
this.name ="Object Operations";
this.s3.accessKeyId = "accessKey";
this.s3.awsSecretKey = "secretKey";
this.s3.defaultLocation="EU";
</cfscript>
<!--- CFImage Stuff --->
<!--- S3 Permissions --->
<cfset perms = [{group="all", permission="read"}]>
<!--- Create the Images ---->
<cfset imageuuid = '#CreateUUID()#'>
<cfset imagefull = '#getid.id#-#imageuuid#-f.jpg'>
<cfset imagemain = '#getid.id#-#imageuuid#-m.jpg'>
<cfset imagethumb = '#getid.id#-#imageuuid#-t.jpg'>
<cfimage action="read" name="img1" source="#imgLink#">
<!--- Create the full size image 505 x N --->
<cfif img1.height GT img1.width>
<cfif img1.width GTE 505>
<cfset ImageResize(img1,'505','')>
</cfif>
<cfelseif img1.width GT img1.height>
<cfif img1.width GTE 505>
<cfset ImageResize(img1, '505','')>
</cfif>
</cfif>
<cfset ImageWrite(img1, "s3://mybucket/#imagefull#")>
<cfset StoreSetACL("s3://mybucket/#imagefull#","#perms#")>
<!--- Create the main size image 251 x N --->
<cfif img1.height GT img1.width>
<cfif img1.width GTE 251>
<cfset ImageResize(img1,'251','')>
</cfif>
<cfelseif img1.width GT img1.height>
<cfif img1.width GTE 251>
<cfset ImageResize(img1, '251','')>
</cfif>
</cfif>
<cfset ImageWrite(img1, "s3://mybucket/#imagemain#")>
<cfset StoreSetACL("s3://mybucket/#imagemain#","#perms#")>
<!--- Create the thumbnail 52 x 52 --->
<!--- resize image to 52 pixels tall if width is greater then height --->
<cfif img1.height GT img1.width>
<cfset ImageResize(img1,'52','')>
<cfset fromX = img1.Height / 2 - 26>
<cfset ImageCrop(img1,0,fromX,52,52)>
<!--- resize image to 75 pixels wide if height is greater then width --->
<cfelseif img1.width GT img1.height>
<cfset ImageResize(img1,'','52')>
<cfset fromY = img1.Width / 2 - 26>
<cfset ImageCrop(img1,fromY,0,52,52)>
<cfelse>
<cfset ImageResize(img1,'','52')>
<cfset ImageCrop(img1,0,0,52,52)>
</cfif>
<cfset ImageWrite(img1, "s3://mybucket/#imagethumb#")>
<cfset StoreSetACL("s3://mybucket/#imagethumb#","#perms#")>
Just realised I hadn't added my solution to this, so here it is- The folder in which the 'non-working' code was in had it's own Application.cfc, which didn't include the S3 elements in the code posted above. The 'working' code did have that in the corresponding Application.cfc.
Not quite sure why that had to be in Application.cfc when it was at the
The this object in application.cfc is an application component and this on ColdFusion page is just a structure variable. Put <cfdump var=#this#> in both places, application.cfc and yourfile.cfm, to see the difference.
The newer versions of ColdFusion (I believe CF 8 and 9) allow you to create structs with object literal notation similar to JSON.
My question is, are there specific benefits (execution efficiency maybe) to using object literal notation over individual assignments for data that is essentially static?
For example:
With individual assignments you would do something like this:
var user = {};
user.Fname = "MyFirstnam";
user.Lname = "MyLastName";
user.titles = [];
ArrayAppend(user.titles,'Mr');
ArrayAppend(user.titles,'Dr.');
Whereas with object literals you would do something like.
var user = {Fname = "MyFirstnam",
Lname = "MyLastName",
titles = ['Mr','Dr']};
Now this limited example is admittedly simple, but if titles was an array of structures (Say an array of addresses), the literal notation becomes awkward to work with.
Before I tried anything, I thought straight away that literals would be faster, as you create everything you need in runtime, and don't need to waste time creating variables, then calling functions to append and all that.
I then wrote a little test that produces a chart with the results. You got me curious there :-)
The results prove I was right, as the graph shows a staggering difference as you can see:
But remember that although one would jump and go with literal notation, I think it's important to remember that literal notation can be awkward, and will most of the times confuse more.
Obviously, if you are developing a page that really needs the speed boost, literal notation is what you're looking for, but be aware that sometimes on CF8, it will produce some strange behaviour.
Just to show you the sort of tests I run:
<cfset aLiterals = arrayNew(1) />
<cfset aDirect = arrayNew(1) />
<cfsilent>
<cfloop from="1" to="10000" index="mm">
<!--- LITERAL --->
<!--- start timer --->
<cfset start = getTickcount() />
<cfloop from="1" to="1000" index="ii">
<cfset user = {Fname = "MyFirstnam", Lname = "MyLastName", titles = ['Mr','Dr']} />
</cfloop>
<!--- end timer --->
<cfset end = getTickCount()>
<!--- Display total time --->
<cfset total = end-start>
<cfset arrayAppend(aLiterals,total) />
<!--- DIRECT --->
<!--- start timer --->
<cfset start1 = getTickcount() />
<cfloop from="1" to="1000" index="jj">
<cfset user = {} />
<cfset user.Fname = "MyFirstnam" />
<cfset user.Lname = "MyLastName" />
<cfset user.titles = [] />
<cfset ArrayAppend(user.titles,'Mr') />
<cfset ArrayAppend(user.titles,'Dr.') />
</cfloop>
<!--- end timer --->
<cfset end1 = getTickCount()>
<!--- Display total time --->
<cfset total1 = end1-start1>
<cfset arrayAppend(aDirect,total1) />
</cfloop>
</cfsilent>
<!--- The cfchart --->
<cfchart format="png" xaxistitle="function" yaxistitle="Loading Time (in secs.)">
<cfchartseries type="bar" serieslabel="literal">
<cfchartdata item="literal" value="#arrayAvg(aLiterals)#">
</cfchartseries>
<cfchartseries type="bar" serieslabel="direct">
<cfchartdata item="direct" value="#arrayAvg(aDirect)#">
</cfchartseries>
</cfchart>
Hope this helps you.
Literal notation is declarative programming, not procedural programming.
With literal notation, you tell the computer what it is you want clearly and in a single step. Without literal notation, you build what you want slowly, piece by piece, and without clarity.
Note that literal notation in CF8 is awkward and flawed, at best. It should rarely be used, and then only in simple cases. The literal notation in CF9 is fine.
For CF8, you can define helpers:
function $A() {
var r = [ ];
var i = 0;
var m = ArrayLen(Arguments);
for(i = 1; i <= m; i += 1)
ArrayAppend(r, Arguments[i]);
return r;
}
function $S() {
return StructCopy(Arguments);
}
And use them as such:
var user = $S(
Fname = "MyFirstnam",
Lname = "MyLastName",
titles = $A('Mr', 'Dr')
);
These helpers work all the time, preserve struct key case (struct keys are not simply uppercased but are cased as you typed them), and nest recursively without bound.