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.
Related
Server: CF10
Platform: Windows and Linux (Win in DEV windows in PROD)
So I'm generating an excel file for a client, but having trouble getting date fields to behave properly. Everything else seems to work just fine but when I send the data to excel to be generated, excel treats it as just text. So when the field gets sorted, it is handled as such.
More info: The column I'm trying to configure is called Arrival Date it is arriving formatted as mm/dd/yyyy. (I have tried to format is as m/d/yy, but when it arrives in the sheet that doesn't work as well.)
Code:
<cfset filename = expandPath("./TDYData_(#DateFormat(now(),'mmddyy')#).xls")>
<!--- Make a spreadsheet object --->
<cfset s = spreadsheetNew("TDYData")>
<!--- Add header row --->
<cfset spreadsheetAddRow(s, "TDY Phase,Full Name,Employment Category,Gender,Originating Agency,Agency Comments,Originating Office,Office Comments,Originating Country,TDY Request Received,Mission Office Supported,Type of TDY Support,eCC Submission,eCC Approval,eCC Point of Contact,Date of Departure from Originating Country,Arrival Date,Departure Date,Accomodation Type,Accomodation Comments,Assigned Desk,Local Mobile Number,TDY Comments")>
<!--- format header --->
<cfset spreadsheetFormatRow(s, {bold=true,fgcolor="lemon_chiffon",fontsize=10}, 1)>
<!--- Add query --->
<cfset spreadsheetAddRows(s, myExcel)>
<cfset SpreadSheetAddFreezePane(s,0,1)>
<cfset SpreadsheetFormatColumn(s, {dataformat="m/d/yy"}, 17) />
<cfset SpreadsheetFormatColumn(s, {alignment="right"}, 16) />
<cfheader name="content-disposition" value="attachment; filename=TDY_Data_(#DateFormat(now(),'mmddyy')#).xls">
<cfcontent type="application/msexcel" variable="#spreadsheetReadBinary(s)#" reset="true">
Any ideas?
Thanks
So I have a workable solution:
<cfset therow = 0>
<cfoutput query="myExcel" startrow="1">
<cfset therow = myExcel.currentrow + 1>
<cfif len(eCCSubDate) GT 0>
<cfset SpreadsheetSetCellFormula(s,"DATEVALUE(#Chr(34)##eCCSubDate##Chr(34)#)",therow,13)>
</cfif>
<cfif len(eCCApproved) GT 0>
<cfset SpreadsheetSetCellFormula(s,"DATEVALUE(#Chr(34)##eCCApproved##Chr(34)#)",therow,14)>
</cfif>
<cfif len(DateDepartCntry) GT 0>
<cfset SpreadsheetSetCellFormula(s,"DATEVALUE(#Chr(34)##DateDepartCntry##Chr(34)#)",therow,16)>
</cfif>
<cfif len(DateArrive) GT 0>
<cfset SpreadsheetSetCellFormula(s,"DATEVALUE(#Chr(34)##DateArrive##Chr(34)#)",therow,17)>
</cfif>
<cfif len(DateDepart) GT 0>
<cfset SpreadsheetSetCellFormula(s,"DATEVALUE(#Chr(34)##DateDepart##Chr(34)#)",therow,18)>
</cfif>
</cfoutput>
So I set a variable for a counter then loop back over the query and check to make sure its not an empty cell then apply a formula to the cell the chr(34) are quotes then the data value. By using this, (After the data is written) it will at least behave like a real date field.
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>
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)
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.
I'm looking to learn how to create a REGEX in Coldfusion that will scan through a large item of html text and create a list of items.
The items I want are contained between the following
<span class="findme">The Goods</span>
Thanks for any tips to get this going.
You don't say what version of CF. Since v8 you can use REMatch to get an array
results = REMatch('(?i)<span[^>]+class="findme"[^>]*>(.+?)</span>', text)
Use ArrayToList to turn that into a list.
For older version use REFindNoCase and use Mid() to extract substrings.
EDIT: To answer your follow-up comment the process of using REFind to return all matches is quite involved because the function only returns the FIRST match. This means you actually have to call REFind many times passing a new startpos each time. Ben Forta has written a UDF which does exactly this and will save you some time.
<!---
Returns all the matches of a regular expression within a string.
NOTE: Updated to allow subexpression selection (rather than whole match)
#param regex Regular expression. (Required)
#param text String to search. (Required)
#param subexnum Sub-expression to extract (Optional)
#return Returns a structure.
#author Ben Forta (ben#forta.com)
#version 1, July 15, 2005
--->
<cffunction name="reFindAll" output="true" returnType="struct">
<cfargument name="regex" type="string" required="yes">
<cfargument name="text" type="string" required="yes">
<cfargument name="subexnum" type="numeric" default="1">
<!--- Define local variables --->
<cfset var results=structNew()>
<cfset var pos=1>
<cfset var subex="">
<cfset var done=false>
<!--- Initialize results structure --->
<cfset results.len=arraynew(1)>
<cfset results.pos=arraynew(1)>
<!--- Loop through text --->
<cfloop condition="not done">
<!--- Perform search --->
<cfset subex=reFind(arguments.regex, arguments.text, pos, true)>
<!--- Anything matched? --->
<cfif subex.len[1] is 0>
<!--- Nothing found, outta here --->
<cfset done=true>
<cfelse>
<!--- Got one, add to arrays --->
<cfset arrayappend(results.len, subex.len[arguments.subexnum])>
<cfset arrayappend(results.pos, subex.pos[arguments.subexnum])>
<!--- Reposition start point --->
<cfset pos=subex.pos[1]+subex.len[1]>
</cfif>
</cfloop>
<!--- If no matches, add 0 to both arrays --->
<cfif arraylen(results.len) is 0>
<cfset arrayappend(results.len, 0)>
<cfset arrayappend(results.pos, 0)>
</cfif>
<!--- and return results --->
<cfreturn results>
</cffunction>
This gives you the start (pos) and length of each match so to get each substring use another loop
<cfset text = '<span class="findme">The Goods</span><span class="findme">More Goods</span>' />
<cfset pattern = '(?i)<span[^>]+class="findme"[^>]*>(.+?)</span>' />
<cfset results = reFindAll(pattern, text, 2) />
<cfloop index="i" from="1" to="#ArrayLen(results.pos)#">
<cfoutput>match #i#: #Mid(text, results.pos[i], results.len[i])#<br></cfoutput>
</cfloop>
EDIT: Updated reFindAll with subexnum argument. Setting this to 2 will capture the first subexpression. The default value 1 captures the entire match.
Try looking into the possibility of making your HTML work with a regular DOM Parser and querying it via XPath instead of hammering this trough an regex-based abomination.
to make HTML input usable, pass it through jTidy (see http://jtidy.riaforge.org/)
Once you have well-formed XML/XHTML, build an XML document from it
<cfset dom = XmlParse(scrubbedHtml, true)>
query the XML document using XPath
<cfset result = XmlSearch(dom, "//span[#class='findme']")>
Done.
EDIT: Coldfusion's XmlSearch() doesn't have great XML namespace support. If you end up producing XHTML instead of the more recommendable XML, use the following XPath (note the colon) "//:span[#class='findme']" or "//*:span[#class='findme']". See here and here for more info.
See the jTidy API documentation for a complete overview what jTidy can do.