Automating watermarking of images with ColdFusion

This post is more than 2 years old.

Earlier this week Bobby H. started an interesting thread over on cf-talk. He was using ColdFusion image processing to automatically watermark a random image in a directory. This worked ok for him, but he had some odd caching issues. One of the things I recommended (and others) was to look at a solution that would permanently watermark the images. ColdFusion is fast and all, but any time you work with files your going to have some slow down. Bobby agreed, but also wanted to keep the convenience of just being able to FTP up an image and have it automatically become one of watermarked images his could could randomly chose from. I thought I'd whip up two quick examples of how this could be done. There are other ways as well, but hopefully these examples will be useful for others.

In my first example, I'm going to create a simple script that will scan one directory for images, apply watermarks, and then copy it to a destination directory. This script can then be set up as a simple scheduled task using whatever interval makes sense. Here is the script:

<cfset depotfolder = expandPath("./depot")> <cfset processedfolder = expandPath("./images")> <cfset watermark = imageRead(expandPath("./watermark.png"))> <cfset watermarkinfo = imageInfo(watermark)>

<!--- First, scan depot for files ---> <cfdirectory directory="#depotfolder#" name="files">

<cfloop query="files"> <cfset theFile = directory & "/" & name> <!--- Is it an image? ---> <cfif isImageFile(theFile)> <cfoutput> #theFile# is an image, will be watermarked.<br/> </cfoutput> <cfset imgFile = imageRead(theFile)> <cfset imgInfo = imageInfo(imgFile)> <!--- paste in the watermark ---> <cfset imagepaste(imgFile, watermark, imgInfo.width-watermarkinfo.width, imgInfo.height-watermarkinfo.height)> <!--- save it and delete original ---> <cfset imageWrite(imgFile, processedfolder & "/" & name, true)> <cffile action="delete" file="#theFile#"> <cfelse> <cfoutput> #theFile# is not an image. Deleting.<br/> </cfoutput> <cffile action="delete" file="#theFile#"> </cfif> </cfloop>

Not much to it, right? I begin with a few simple definitions: my depot folder (where images were FTPed or copied up), the destination folder, and the actual watermark. I get all the files from the depot, and for each, I check to see if it is an image. If so, we simply use the imagePaste function to copy our watermark over. I do a small bit of math to place the watermark in the lower right hand corner. Here is an example:

Pretty simple, right? Obviously the output statements were just for testing. I won't see them when this is run as a scheduled task, but I can log the results so it might still be useful. This works ok, but I also thought it might be nice to create a version that makes use of an Event Gateway. Event Gateways are one of those ColdFusion features that really didn't take off, and that's kind of sad. They are pretty darn powerful and allow your server to do things it normally could not. One of the sample gateways that ship with ColdFusion is the directory watcher. Now I call that a "sample" gateway but don't take that to mean it is just there to demonstrate the feature. This gateway is very useful and could be used in any production site. The directory watcher, as you may guess, monitors a directory for changes. So anytime a file is added, edited, or deleted, ColdFusion can then run a CFC to process those changes. Our need here (auto watermarking images) is a perfect example of that. I began by going to my ColdFusion Administrator and creating a new instance of the gateway.

I've got both an INI file and a CFC file declared. The INI file, at minimum, must specify the folder to watch. I specified an interval as well so it would run a bit quicker then the usual 60 seconds:

directory=c:/webroot/autowatermark/depot interval=30000

The component then is simply our last script rewritten in CFC format (and script format). I've put the code within a method called onAdd, which is the default method called when a file is added to a directory. Outside of that though nothing really else has changed:

component {

public void function onAdd(required struct cfevent) {
	//for debugging
	writelog(file='application',text='autowatermarker #serializejson(arguments.cfevent.data)#');
	var theFile = arguments.cfevent.data.filename;
	
	var processedfolder = expandPath("./images");

	if(isImageFile(theFile)) {
		imgFile = imageRead(theFile);
		imgInfo = imageInfo(imgFile);
		
		var watermark = imageRead(expandPath("./watermark.png"));
		var watermarkinfo = imageInfo(watermark);
		imagepaste(imgFile, watermark, imgInfo.width-watermarkinfo.width, imgInfo.height-watermarkinfo.height);
		imageWrite(imgFile, processedfolder & "/" & getFileFromPath(theFile), true);
		fileDelete(theFile);
	} else {
		fileDelete(theFile);
	}

}

}

And that's it. Once my event gateway was running, I could drag any number of files into the folder and just sit back and wait. Within 30 seconds they were all "noticed" by the event gateway, watermarked, and copied over. Like magic. :)

Ok, so that's it really, but I want to leave you with a few issues I ran into when playing with this gateway. This really isn't on topic to the original need, but these things tripped me up so I figured I'd share.

First - I had a lot of issues getting my ini file set up right. I noticed when I started my gateway I kept getting a failed response. Unfortunately, the CF Admin refused to tell me why the gateway failed. Turns out the gateway logged to a file called watcher.log. This was available/viewable via the admin. In there I saw that I had a typo in the folder name. Not quite as obvious was that I also needed to switch from \ format to / format for the paths. This is something I recommend in CF code anyway as it is portable across operating systems.

Secondly - the docs seems to imply that if you only care about files added, then you can forget about writing the delete/update functions. However, when my CFC code deleted the images while processing, the gateway tried to run the delete handler. I never saw an error, but one was logged. This is harmless - but if you are anal retentive like me, you will probably want to create shell functions just to ensure nothing is logged.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Misty posted on 6/4/2010 at 8:14 PM

Hi The New CFC in a Script i a NEW CF9 Tag. Can the same in be in Cffunction type. AS that counts really easy to understand. If you can Provide a Sample of that too. it will be awesome!

Comment 2 by Raymond Camden posted on 6/4/2010 at 8:16 PM

Yes, it could be in tags too. It's the exact same code though - just tag based. If you _really_ want me to do this, I will. ;)

Comment 3 by Misty posted on 6/4/2010 at 8:41 PM

Please! I am seeing CF is becomeing day by day more like Java as it is native to it! if the same with CFfunction can u show here, It will help in seeing the exact diffrence how to do the same in CFFunction and cfscript and see its difference and processing time too!

Regards

Comment 4 by Raymond Camden posted on 6/4/2010 at 11:19 PM

I didn't run this - just confirmed it had no syntax errors.

<cfcomponent output="false">

<cffunction name="onAdd" returntype="void" access="public">
<cfargument name="cfevent" type="struct" required="true">

<cflog file="application" text="autowatermarker: #serializejson(arguments.cfevent.data)#">
<cfset var theFile = arguments.cfevent.data.filename>
<cfset var processedfolder = expandPath("./images")>

<cfif isImageFile(theFile)>
<cfset imgFile = imageRead(theFile)>
<cfset imgInfo = imageInfo(imgFile)>

<cfset var watermark = imageRead(expandPath("./watermark.png"))>
<cfset var watermarkinfo = imageInfo(watermark)>
<cfset imagepaste(imgFile, watermark, imgInfo.width-watermarkinfo.width, imgInfo.height-watermarkinfo.height)>
<cfset imageWrite(imgFile, processedfolder & "/" & getFileFromPath(theFile), true)>
<cfset fileDelete(theFile)>
<cfelse>
<cfset fileDelete(theFile)>
</cfif>

</cffunction>

</cfcomponent>

Comment 5 by Misty posted on 6/5/2010 at 5:07 AM

Thanks a Ton:

well these two lines were confusing while the cffunction based CFC was not there

<cfargument name="cfevent" type="struct" required="true">

<cflog file="application" text="autowatermarker: #serializejson(arguments.cfevent.data)#">

but now they are all clear

Cheers

Comment 6 by Raymond Camden posted on 6/5/2010 at 6:49 PM

The <cfargument> tag matches to (required struct cfevent), and the <cflog> tag matches to writelog.

Comment 7 by Joshua Siok posted on 6/5/2010 at 11:47 PM

Great example Ray,
I've done a lot of similar tasks with PDFs and have some suggestions for you.

If you are using the event gateway, I would suggest adding onChange and onDelete functions in the CFC. Anyone using the system that didn't write the code would expect that when an image is updated or deleted in the "depot" folder, it would also change or delete the image in the "images" folder. This could be important for business/legal reasons.

Also, If you're taking the .CFM approach and running a scheduled task, I would suggest using the DateLastModified property of your cfdirectory results to do some directory comparing of the depot and images folders to determine what files were added or more recently updated and create a new query to loop through. This will reduce the overhead of re-processing all the files that didn't change. Similarly, you might want to determine if any images exist in the images folder that are not in the depot folder and delete them.

Comment 8 by Raymond Camden posted on 6/5/2010 at 11:50 PM

To your first part - note that I move files from the depot. Therefore, nothing can get updated there. However - also note that when I copy the new image, I overwrite any existing image in the destination. So to do an update to foo.jpg, you just reupload it to the depot.

To your second point... again.. .maybe I'm missing something, but since I move files -out- of the depot, there is no need to scan for dates?

Comment 9 by Joshua Siok posted on 6/6/2010 at 12:05 AM

Sorry Ray, I didn't notice the fileDelete(theFile) call in there. I guess I was assuming the depot folder would be an FTP or windows folder share where someone might have a working set of files and may want to have access to the originals.

Comment 10 by Raymond Camden posted on 6/6/2010 at 12:07 AM

No worries. Thought maybe I was missing something there. ;)

Comment 11 by Vae Victis posted on 6/14/2010 at 10:12 PM

Ray

Great articles as always. I am trying something similiar on my local machine (minus the watermark) with CF Standard. However, my CFC does not seem to get triggered any thoughts and how to debug?

Comment 12 by Raymond Camden posted on 6/14/2010 at 10:14 PM

Did you start the event gateway instance?

Comment 13 by Vae Victis posted on 6/14/2010 at 11:29 PM

Ray

The it is running and all I see in the logs is that it has initialized.

Comment 14 by Raymond Camden posted on 6/14/2010 at 11:31 PM

Ok, did you check the directory watcher log too? For me it showed additional notes - like when I had a bad directory.