Art Site: cfimagination

Got the image stored in the database now, sweet. Next and final step is to get it back out. It's really as simple as resizing it to what I want and cfimage'ing it to the browser.

<cfset ArtImage = ImageNew(request.artDetails.getArtImage())>
<cfset ImageResize(ArtImage,'','200')>
<cfset ImageAddBorder(ArtImage,2)>
<cfimage action="writeToBrowser" source="#ArtImage#" format="jpg">

This will resize the image to 200 pixels wide, give it a border, and write it to the browser as a jpg. But a few problems. What if it's a skinny bit of art? If it's only 150 pixels to start with and 800 pixels tall, I don't want to make it bigger. How do I set an alt tag to the img tag that. To solve some of these, I hit up my man Ben Nadel's blog, doing a search for "cfimage". I made some changes to his ImageWriteToBrowserCustom() function. In this version... The BORDER argument will add a border with Image functions, instead of styling the image. A watermark will be added if you want. The image size will be bound by the width and height given, rather than resizing to both arguments. And a little JavaScript will be added for later use.

<cffunction name="ImageWriteToBrowserCustom"
   access="public"
   returntype="void"
   output="true">


   <cfargument name="Image" type="any" required="true" />
   <cfargument name="Alt" type="string" required="false" default="" />
   <cfargument name="Class" type="string" required="false" default="" />
   <cfargument name="Style" type="string" required="false" default="" />
   <cfargument name="Height" type="string" required="false" default="" />
   <cfargument name="Width" type="string" required="false" default="" />
   <cfargument name="Border" type="string" required="false" default="" />
   <cfargument name="WaterMark" type="boolean" required="false" default="true" />
   <cfargument name="Clickable" type="boolean" required="false" default="false" />

   <cfset var LOCAL = {} />
   
   <cfset LOCAL.myImage = ImageNew(ARGUMENTS.Image)>
   
   <cfif arguments.width neq "" and LOCAL.myImage.getWidth() gt arguments.width>
      <cfset ImageResize(LOCAL.myImage, arguments.width, "")>
   </cfif>
   <cfif arguments.height neq "" and LOCAL.myImage.getHeight() gt arguments.height>
      <cfset ImageResize(LOCAL.myImage, "", arguments.height)>
   </cfif>
   <cfif arguments.Border neq "">
      <cfset ImageAddBorder(LOCAL.myImage, arguments.border)>
   </cfif>
   
   <cfif arguments.WaterMark>
      <cfset LOCAL.theWatermark = ImageNew(request.watermark)>
      
      <!-- center the watermark at the bottom of the image -->
      <cfset ImagePaste(LOCAL.myImage,
         request.watermark,
         (LOCAL.myImage.GetWidth() - LOCAL.theWatermark.GetWidth())/2,
         (LOCAL.myImage.GetHeight() - LOCAL.theWatermark.GetHeight()))>

   </cfif>


   <!---
      Write the image to the browser. This is really just
      creating the image and then writing to the buffer.
      All we have to do is intercept the buffer write.
   --->

   <cfsavecontent variable="LOCAL.Output">
      <cfoutput>

         <!--- Write image tag. --->
         <cfimage
            action="writetobrowser"
            source="#LOCAL.myImage#"
            format="jpg"
            />


      </cfoutput>
   </cfsavecontent>

   <!---
      First, delete any existing attributes that we might
      be using (so that we can just add new ones).
   --->

   <cfset LOCAL.Output = LOCAL.Output.ReplaceAll(
      "(?i) (alt|class|style|height|width|border)=""[^""]*""",
      ""
      ) />


   <!---
      Now that we have an image with Just the SRC
      attribute, we can go about adding our attributes.
      First, chop off the trailing slash.
   --->

   <cfset LOCAL.Output = LOCAL.Output.ReplaceFirst(
      "\s*/?>\s*$"
,
      ""
      ) />

   <!---
      Loop over the arguments to see if we need to
      add them to the tag.
   --->

   <cfloop
      index="LOCAL.Key"
      list="alt,class,style"
      delimiters=",">


      <!--- Check for a passed-in value. --->
      <cfif Len( ARGUMENTS[ LOCAL.Key ] )>

         <!---
            Append this argument to the output and a
            key-value attribute.
         --->
         <cfset LOCAL.Output &= (
            " " &
            LOCAL.Key &
            "=""" &
            ARGUMENTS[ LOCAL.Key ] &
            """"
            ) />


      </cfif>

   </cfloop>
   
   <cfif arguments.Clickable>      
      <cfset LOCAL.Output &= (
         " onClick=""ColdFusion.Window.show('myImage')"""
         ) />

   </cfif>

   <!--- Write the image tag to the output. --->
   <cfset WriteOutput( LOCAL.Output & " />" ) />

   <!--- Return out. --->
   <cfreturn />
</cffunction>

Blah blah blah, bunch of code. There's some easy enhancements that could be made but for the purposes of this project, they weren't needed. The watermark pulls from request.watermark, would be better if that were passed in. Would be nice if there were another argument to set if the resize is done as a bounding box or absolute resizing. This code assumes the image passed in will always be larger than what ends up being printed. Adding the watermark could be split into another function to tidy this up some. Should resize the watermark to fit the image given...

But using Ben's function, we now have an alt attribute on the image tag in the generated HTML, hurrah! Now I have very consistent image generation across the few pages that need it. To put in an image with a watermark, borders, alt tag, resized as needed...

<cfset ArtImage = ImageNew(request.artDetails.getArtImage())>
<cfset createObject("component","jwa.model.udf").ImageWriteToBrowserCustom(
   Image=ArtImage,
   Alt="#request.artDetails.getArtTitle()#",
   Border="2",
   Width="300",
   Clickable="true"
   ) />

The code that's added to the image tag if arguments.Clickable is true is fun. I needed something like "click here to enlarge". So I put a larger version of the image (same tag as above with larger dimensions) in a cfwindow called myImage. Would have been slicker to use some JavaScript library that I just copy/paste into place. But I've been doing Flex for so long, I wanted this project to be all CF and all code that I fully understand.

Doing all this reminds me of the pre-CF8 days (for me that was 2-3 weeks ago). I have an image.cfc sitting in my webroot that can do all sorts of stuff to images using Java voodoo space magic. cfimage takes away the need for most voodoo space magic, but still would be nice to build up a library of image functions. One for adding a watermark. One for resizing in a bounding box. One for a few image transformations I regularly do (resize, watermark, AND border)...

Art site: Where to put the images

CFIMAGE!! Woooo!!

I about 6 months behind on my ColdFusion, this was my first time to really use cfimage. So imagine my excitement. First, very simple form for selecting and uploading an image.

<cfform enctype="multipart/form-data">
   Image File: <cfinput type="file" name="ArtImage"/><br/>
   <cfinput type="submit" name="submit" value="Update Art"/>   
</cfform>

The action to be taken...

<cfif form.ArtImage neq "">
   <cffile action="upload"
         destination="#path#\"
         filefield="ArtImage"
         nameconflict="makeunique">
   
   
   <cfimage action="read" source="#path#\#cffile.serverfile#" name="ThisPicture"/>
   
   <cfif ThisPicture.width gt 800>
      <cfscript>
         ImageResize(ThisPicture,'800','');
      </cfscript>
   </cfif>
   
   <cfset myImage=ImageNew(ThisPicture)>
   
   <cffile action="delete" file="#path#\#cffile.serverfile#"/>

</cfif>

Step 1: Get the image into memory. This is done by using cffile, then cfimage to read it back out.

Step 2: Resize. A 1700 pixel wide image might get uploaded. But since it will never display 1700 pixels wide anywhere in the site, there is no need to save a 1700 pixel wide image.

At the end of this, the image is resized and sitting in variables.myImage, ready to be saved to the database. The field type in SQL Server 2k5 is "image"

<cfscript>
   newart = CreateObject("component","jwa.model.art.Art").init();
   if(isDefined('myImage') AND isImage(myImage)) newart.setArtImage(ImageGetBlob(myImage));
   
   variables.newartSaved = CreateObject("component","jwa.model.art.ArtGateway").save(newart);
</cfscript>

Then through some CFC magic, this object ends up at a cfquery, where the image is defined as a CF_SQL_BLOB.

<cfquery name="qCreate" datasource="#request.dsn#">
   insert into dbo.jwaArt(ArtImage)
   values (
      <cfqueryparam value="#getArtImage()#" cfsqltype="CF_SQL_BLOB"/>
   )
   select @@identity as ArtID
</cfquery>

And there you go, the images are in the database. It looked a lot harder than it turned out to be.

Next: Getting the images out

Art site: Parsing the URL for SES

For this art website, there are categories of art and there are pieces of art. If you're looking at some art in the Mackinac category, my options were

art.cfm?cat=3&art=47
art.cfm?category=dmackinac&art=harbor
art.cfm/mackinac/harbor

and of course the third is easiest to read and understand. That is to assume that any average web user ever looks at the URL. The third is called "search engine safe" (SES).

A Harbor Springs' Early Day

To accomplish this, you have to parse the URL string. In most web server environments, anything passed with and after the file name goes into the cgi.path_info variable. cflib.org has a parseSES() function, but it was wasn't exactly what I wanted. I wasn't passing name-value pairs in the URL, just values. Unless someone is hacking the URL, the category will always be first, then the art name. So I changed the parseSES() function to return the stuff passed in the URL as an array as well as the name-value pairs.

<cffunction name="parseSES">
   <cfscript>
      var LOCAL=StructNew();
      LOCAL.pathInfo = reReplaceNoCase(trim(cgi.path_info), '.+\.cfm/? *', '');
      LOCAL.i = 1;
      LOCAL.lastKey = "";
      LOCAL.value = "";
      LOCAL.url = StructNew();
      LOCAL.urlArray = ArrayNew(1);

      if(not len(LOCAL.pathInfo)) return LOCAL;
      
      for(LOCAL.i=1; LOCAL.i lte listLen(LOCAL.pathInfo, "/"); LOCAL.i=LOCAL.i+1) {
         LOCAL.value = listGetAt(LOCAL.pathInfo, LOCAL.i, "/");
         ArrayAppend(LOCAL.urlArray, LOCAL.value);
         
         if(LOCAL.i mod 2 is 0) LOCAL.url[LOCAL.lastKey] = LOCAL.value;
         else LOCAL.lastKey = LOCAL.value;
      }
   Çspan style='color: #808080'ÈÇemÈ   //did we end with a "dangler?"
Ç/emÈÇ/spanÈ      if((LOCAL.i-1) mod 2 is 1) LOCAL.url[LOCAL.lastKey] = "";
      
      return LOCAL;
   </cfscript>
</cffunction>

So on to the implementation! In Application.cfc, in the OnRequestStart() method, first thing is to call urlvars=parseSES(). From the length of the array of values returned in that, I know what the user's trying to do.

ArrayLen(urlvars.urlArray) eq 0 nothing selected
ArrayLen(urlvars.urlArray) eq 1 category selected
ArrayLen(urlvars.urlArray) eq 2 art selected

I went as far as querying for all the category and art details in OnRequestStart. This worked out well because then the category and art names were available when I printed the page title.

Next: Where to put images

Art site: What is it?

My cousin (technically mom's cousin) Jim Williams is a artist specializing in various Michigan locales and Chicago. His art is for sale all over on Mackinaw Island and even Oprah bought a piece from him. Most of his art is sold in stores or at art shows, but some sales come from the website. The existing site was made in FrontPage, which says a lot about a site.

Jim's wife contacted me about sprucing up the site, making it easier for search engines to find it, etc. The site breaks the art pieces into categories (Detroit, Chicago, Mackinaw, etc). Each category has a page with thumbnails and the name of the art. Then each piece of art has its own page with a larger image, more details, and prices.

The main issue of the site is FrontPage. It sucks. I had to take the site out of needing to have files saved and edited on the harddrive. It was causing constant problems publishing the correct version of a file, overwriting changes, blah blah blah. This of course means saving the site info in a database

Another issue in that having images of the art on the site is that of keeping people from just printing out their own copies and not buying the art. This is solved by slapping a watermark on the image files. But I didn't want to have to put the watermark on before uploading it to the site. And I didn't want to have to save a number of copies of the image, one for each page that it's displayed at a different size.

And last, the URL has to be coherent. "art.cfm?id=14" is no good.

Next: Parsing the URL for SES

BlogCFC was created by Raymond Camden. This blog is running version 5.9.002. Contact Blog Owner