Learning ColdFusion 8: CFImage Part III - Watermarks And Transparency
ColdFusion 8 has introduced the CFImage tag and dozens of image manipulation functions. We have already looked at reading and writing image files and we have covered the basic yet powerful image manipulation that is possible through the CFImage tag actions alone. Now we know enough to be able to really start exploring the vast feature-set of ColdFusion 8 image manipulation. When it comes to something like this, your imagination is really the only limit, so it makes it a little bit more difficult to come up with simple ways to learn this stuff. I find that task-based learning works well and will allow us to accomplish real world goals while touching on many aspects of image manipulation.
For this post, we will cover adding watermarks to photos. There are a number of ways to do this including building the watermark from scratch and using an existing watermark image. To start with, I am going to demonstrate using an existing watermark image. Hands down, this is going to give you the best results. ColdFusion 8 provides amazing image manipulation, but it is certainly not a replacement for an image / photo editing application (and that was certainly never their intention) such as Adobe Fireworks.
To create my watermark image, I am going to open up my sweet-ass Fireworks application and create a transparent canvas. The transparent canvas is important because we don't want the overlaid image to be too obvious. Then, I am going to type out "Kinky Solutions" and give it an nice, orange glow affect:
Notice that the background of this canvas (in Adobe Fireworks) has that checkered white and gray pattern. This signifies that the background is transparent. Then, I export this image as a PNG32. This will compress the image while maintaining the transparency.
Now, let's take a look at the image that we are working with and the watermark that we are going to apply:
<!--- Read in the original image. --->
<cfset objImage = ImageRead( "./blue_eyes.jpg" ) />
<!--- Write it to the browser. --->
<cfimage
action="writetobrowser"
source="#objImage#"
/>
<!--- Read in the Kinky Solutions watermark. --->
<cfset objWatermark = ImageNew(
"./kinky_solutions_watermark_png32.png"
) />
<!--- Write it to the browser. --->
<cfimage
action="writetobrowser"
source="#objWatermark#"
/>
Running the above code, we get the following output:
The watermark at this time is on a white background so you can't tell that it is transparent, but as you will see soon, the PNG image format keeps the same transparency that we had in our Fireworks application. Ok, so now what we're gonna do is read in both images and then paste the watermark onto the image of the girl with blue eyes (NOTE: I am adding a watermark for demonstration purposes, this photo is not my property nor the property of Kinky Solutions).
<!--- Read in the original image. --->
<cfset objImage = ImageRead( "./blue_eyes.jpg" ) />
<!--- Read in the Kinky Solutions watermark. --->
<cfset objWatermark = ImageNew(
"./kinky_solutions_watermark_png32.png"
) />
<!---
Turn on antialiasing on the existing image
for the pasting to render nicely.
--->
<cfset ImageSetAntialiasing(
objImage,
"on"
) />
<!---
When we paste the watermark onto the photo, we don't
want it to be fully visible. Therefore, let's set the
drawing transparency to 50% before we paste.
--->
<cfset ImageSetDrawingTransparency(
objImage,
50
) />
<!---
Paste the watermark on to the image. We are going
to paste this into the bottom, right corner.
--->
<cfset ImagePaste(
objImage,
objWatermark,
(objImage.GetWidth() - objWatermark.GetWidth()),
(objImage.GetHeight() - objWatermark.GetHeight())
) />
<!--- Write it to the browser. --->
<cfimage
action="writetobrowser"
source="#objImage#"
/>
The above code uses some cool functions. After we read in the images, the first thing we do is turn on anti aliasing for future draw actions to the primary image. Anti aliasing gives things a slightly fuzzy look that can be more pleasing to the eye and help manipulations to appear more natural. The ColdFusion 8 documentation recommends turning this on for Paste actions. I would say that is especially true in this case where our watermark has a fuzzy edge due to the Glow affect we applied.
Whether you are setting the drawing anti aliasing, the background color, the drawing color, or the line stroke properties, understand that you are setting these properties for a specific image, not all images. Also understand that these properties will hold true for all image manipulation actions made to that specific image going forward or until the properties are explicitly changed.
Before we paste the watermark onto the original image, we set the drawing transparency to be 50 percent. This does not make the current image transparent; this makes all future draw actions 50% transparent when they are applied to the current image. The idea here is that not only are we using a watermark with a transparent background, when we paste it into the current image, we are going to adjust its alpha channel so that some of the original image shows up through the watermark.
The paste action itself is rather simple. It takes the source image, the target image, and the X and Y coordinates of where to paste the target image. The target image (second argument) is always pasted onto the source image (first argument). The X and Y coordinates are just like a web page - they start at the top left (0,0) and increase going down and to the right.
When I paste in the watermark, I am using the both the dimensions of the source and target images to calculate the X and Y coordinates of the paste. Notice that I am use the .GetWidth() and .GetHeight() actions. These are the undocumented, underlying Java methods available to the ColdFusion image object. If you are not comfortable, you can replace these with the more verbose ImageGetWidth() and ImageGetHeight() ColdFusion 8 methods.
Running the above code, we get a really nice Kinky Solutions watermark affect:
Pretty slick. There is some graininess around the watermark, but in all fairness the glow effect plus the transparency plus the JPG optimization is just a bad combination for small file sizes. If I had exported this image as a PNG rather than a JPG, the quality would have been higher. So much of image manipulation magic is finding the right balance between quality and image size. You want things to look sweet, but you also don't want a 100K image that has no compression.
So what happens if you don't have an application like Adobe Fireworks to make your watermark image? Not all is lost. You can still create a watermark image manually and then paste it in using a similar technique. For this next demo, we are going to create a second image using ColdFusion 8 that has a gray box with the text, "Kinky Solutions," centered on it. Then, just as we did in the previous example, we are going to paste it over the original image:
<!--- Read in the original image. --->
<cfset objImage = ImageRead( "./blue_eyes.jpg" ) />
<!---
Create a new image for the watermark with the given
dimensions and give it a very light gray cavnas color.
--->
<cfset objWatermark = ImageNew(
"",
100,
20,
"rgb",
"##F0F0F0"
) />
<!---
Set the drawing color. This will be the color
for all image manipulations going forward.
--->
<cfset ImageSetDrawingColor(
objWatermark,
"##666666"
) />
<!--- Draw the rectangle. --->
<cfset ImageDrawRect(
objWatermark,
0,
0,
(objWatermark.GetWidth() - 1),
(objWatermark.GetHeight() - 1)
) />
<!---
Set text drawing anti aliasing. This will give are
text a smoother rendered look (rather than jagged
text you would see on a web page).
--->
<cfset ImageSetAntialiasing(
objWatermark,
"on"
) />
<!--- Create text attributes. --->
<cfset objAttributes = {
Font = "Verdana",
Size = "10",
Style = "Italic"
} />
<!--- Draw the watermark text onto our watermark image. --->
<cfset ImageDrawText(
objWatermark,
"Kinky Solutions",
11,
14,
objAttributes
) />
<!---
When we paste the watermark onto the photo, we don't
want it to be fully visible. Therefore, let's set the
drawing transparency to 50% before we paste.
--->
<cfset ImageSetDrawingTransparency(
objImage,
50
) />
<!---
Paste the manually created watermark image onto the photo.
This time, we don't need to turn on any anti aliasing since
the watermark has solid borders. The anti alisasing only
helps us when the pasted image has anti aliased perimeter.
--->
<cfset ImagePaste(
objImage,
objWatermark,
(objImage.GetWidth() - objWatermark.GetWidth() - 3),
(objImage.GetHeight() - objWatermark.GetHeight() - 3)
) />
<!--- Write the image with the new watermark to the browser. --->
<cfimage
action="writetobrowser"
source="#objImage#"
/>
Now, instead of reading in a watermark image, we are creating an entirely new image canvas using ImageNew(). ImageNew() takes the target image (left blank since we are creating it from scratch), the width and height, the image type, and the canvas color. For this image, we are using an RGB color set. This method can also take an ARGB color set, but the documentation is not very clear on what that is. I assume that the "A" stands for "Alpha", but I could not figure out how to make a transparent canvas (we will discuss doing this later on).
Once we have our canvas ready, we set our drawing color and then draw our rectangle, which is really just a border. I am using the ImageDrawRect() method rather than the ImageAddBorder() method as adding a border actually changes the size of the canvas; drawing a rectangle, on the other hand, gives us complete freedom to create a border of any dimension we want within the current canvas size. I am leaving out the sixth ImageDrawRect() argument which is a flag for filling the rectangle (with the same color as the border itself - the drawing color); we want the light gray of the canvas color to show through.
Next, we draw the Kinky Solutions text to the watermark. Before this action, we turn on the anti aliasing to give the text a more pleasing rendering. When we draw the text onto the canvas, we give it the X and Y coordinate and the text properties. The properties affect the font and size, but do not affect the text color. Text color is determined by the general drawing color set for the canvas (ie, SetDrawingColor()). Also, it is important to understand how text is drawn. I am not sure why this is exactly, but when drawing the text, the X and Y coordinates are for the bottom, left point of the text NOT the top, left. We are drawing our text at (14, 11), which you can clearly see is the bottom, left coordinate:
And again, when we paste this watermark onto the source canvas, we want to paste with a 50% transparency. Notice this time, though, that we did not turn on anti aliasing for the source canvas. Our watermark has solid edges and a border. We don't really need this edge to be anti aliased.
Running the code above, we get:
Clearly, this is not as nice as the first demo (at least in my opinion), but it does give you the ability to create a decent watermark if you don't have Fireworks. Also, we could technically have just drawn the watermark rectangle and the text directly onto the source image (the girl with the blue eyes) without having to create a watermark canvas; but, by creating a second canvas, I am able to demonstrate more ColdFusion 8 functionality.
In our first demo, we dealt with a PNG watermark that had a transparent canvas. In our second demo, we dealt with a watermark that had a rectangular, opaque canvas. What happens if we want to create a transparent canvas from scratch? There may be a way to create a transparent canvas inherent to ColdFusion 8, but I could not find it in the documentation or figure it out on my own. Transparent canvases, however, are really useful for things like watermarking, so being able to create them is important. In this next demo, I will show you how to create a transparent canvas using a neat little hack.
This transparent canvas hack relies on the fact that ColdFusion 8 can read in image data stored in a Base64 encoding. What we do it read in a new image using Base64 data that represents a 1x1 transparent GIF image. This will create an image that is 1x1 with a transparent canvas. Then, we just need to resize the image to be the dimensions of our desired canvas.
In this next demo, we will create a transparent watermark canvas from scratch using the above hack. Then will we will draw the text, "Kinky Solutions," blur the canvas, and then paste it over the source image:
<!--- Read in the original image. --->
<cfset objImage = ImageRead( "./blue_eyes.jpg" ) />
<!---
Read in the spacer image. This is the base 64 encoding
of a 1x1 transparent spacer GIF. This will allow you
to create a transparent canvas without having to have
the spacer image on-hand. Here, I am putting in the
base64 headers for demonstration (but they are not needed).
--->
<cfset objWatermark = ImageReadBase64(
"data:image/gif;base64," &
"R0lGODlhAQABAIAAAP///////yH5BAEHAAEALAAAAAABAAEAAAICTAEAOw=="
) />
<!---
Resize the transparent image to be the size of the
target canvas. This will result in a larger,
transparent canvas.
--->
<cfset ImageResize(
objWatermark,
190,
30
) />
<!---
Set text drawing anti aliasing to be on. We are
going to be writing text and then blurring it, so
we want to let it be a little fuzzy.
--->
<cfset ImageSetAntialiasing(
objWatermark,
"on"
) />
<!--- Set the drawing color to be white. --->
<cfset ImageSetDrawingColor(
objWatermark,
"##FFFFFF"
) />
<!--- Create text attributes. --->
<cfset objAttributes = {
Font = "Arial Black",
Size = "20"
} />
<!--- Draw the water mark text. --->
<cfset ImageDrawText(
objWatermark,
"Kinky Solutions",
11,
20,
objAttributes
) />
<!--- Blur the watermark image. --->
<cfset ImageBlur(
objWatermark,
6
) />
<!---
Set text drawing anti aliasing to be on for the
target image. Since the image we are pasting in,
we want a slight blurring.
--->
<cfset ImageSetAntialiasing(
objImage,
"on"
) />
<!---
Paste the manually created watermark image onto the photo.
This time, we don't need to turn on any anti aliasing since
the watermark has solid borders. The anti alisasing only
helps us when the pasted image has anti aliased perimeter.
--->
<cfset ImagePaste(
objImage,
objWatermark,
(objImage.GetWidth() - objWatermark.GetWidth()),
(objImage.GetHeight() - objWatermark.GetHeight())
) />
<!--- Write the image with the new watermark to the browser. --->
<cfimage
action="writetobrowser"
source="#objImage#"
/>
Notice that the image we are reading in is the Base64 data:
R0lGODlhAQABAIAAAP///////yH5BAEHAAEALAAAAAABAAEAAAICTAEAOw==
This is the encoded 1x1 transparent GIF. We are also including the headers in the Base64 data, but this is not required (it works if you take it out); I left it in purely for demonstration purposes. Because we are using Base64 data, rather than an actual GIF image, it allows us to create transparent canvases without having a physical image on hand.
Running the above code, we get:
Clearly, this is a hack. I am sure that ColdFusion 8 has a way to do this which is more elegant, but for now, until I can figure it out, this is a rather simple work-around. I hope, however, that you are beginning to see how amazing the ColdFusion 8 image manipulation functionality is.
Want to use code from this post? Check out the license.
Reader Comments
Very nice work, I particulary like the first watermark. :)
And the transparent hack was a good idea also.
Good work
Your tutorial was very helpful for me today, but I did find a way to do a transparent canvas that is not a hack. http://tutorial21.learncf.com/ states that if you omit the color argument from ImageNew, the image is transparent. You then set your ImageSetDrawingTransparency to the desired transparency for what you are going to paste into it and it's done.
Keep up the great work.
Thanks for this post, I just added the watermark feature to my photo upload page and it worked great. I couldn't have done it with out your help.
Dave
@Dave,
That's awesome mate; glad to help!
Hi Ben!
This feature is great! I used it to protect the images in my webshop. In most cases (99%) it works fine. However, sometimes I get an error message and images do not show up:
file or directory D:/FTPRoot/euroof.hu/WWW/WEB-INF/railo/temp//graph/F0669616-0F45-476A-A41BD0733A999E55.png not exist
The error occurred in D:\FTPRoot\euroof.hu\WWW\WEB-INF\railo\context\graph.cfm: line 1
1: <cfcontent file="#GetTempDirectory()#/graph/#url.img#" type="image/#url.type#"><cfsetting showdebugoutput="no">
Luckily it does not cause error maessages appear on the clients' screen. The only reason I got to know this error exists, is that I send all the appearing errors on my site to myself in email automatically. (I edited error_neo.cfm a bit).
Do you have any idea why this is happening?
I couldn't figure it out yet.
Thanks:
Viktor from Hungary
How would I go about centering this watermark? I have been trying to figure it out for a little while and haven't come up with anything yet.
Thanks!
Nathan
BTW Nice meeting you at CFUnited!
@Viktor,
Not sure if this has something to do with it, but the resultant file path has a double slash:
"/temp//graph"
... could that be causing the problem?
@Nathan,
If the watermark is another image file, then you can center is using the dimensions of watermark image and the target image. However, if the watermark is text-based, then it becomes a lot more complicated as you actually need to calculate the width / height of the text in order to center it. That's a lot of complication for no reason (unless your watermark needs to be dynamic). As such, I would go with an image-based watermark whenever possible.
Thanks Ben, I am new to cf and with your help I am learning step by step.....thank's a lot. There are not many authors on internet like you, thanks again
@Swapnil,
If you search for just about anything ColdFusion, @Ben's site comes up first thing in the results. It's one of the most visible, thorough and helpful ColdFusion site out there. You probably already know this, though. He has been a great help to so many of us.
Ben,
I've been using this watermark code for some time now. However I recently changed my code so that my users member profile names were appended to the end of a domain name for my site to point to their vanity url's. So basically I went from a short... HotBoatDeals.com to a long HotBoatDeals.com/ChicagoYachtCharters < -- that one is a recent member that joined. I have the positioning set to like 460,30 which had worked fine for most of my recent members but I noticed that the rs of this members profile name is cut or cropped off on the right side of the pictures. How would I go about having the watermark start at the bottom left corner of each and every picture rather than number of pixels from the bottom right corner. This way I don't have to keep increasing my pixel number more and more to keep all the letters showing up. Also, is there a way to scale the size of the font according to the size of the image? If I get someone that uploads a picture that has already been preshrunk or is in landscape view, it doesn't always work right. Or rather the font is huge and crops off much of the watermark.
Thanks again,
Great Site and Huge CF Help
As always,
Branden deBuhr
Hi Ben,
Just to ask currently it is placed bottom right corner, if i need to replace the same rendered image on the bottom left side or in the bottom center, how that can be calculated.
bottom center will be something i am trying too