Skip to main content
Ben Nadel at Scotch On The Rock (SOTR) 2010 (Amsterdam) with: Terrence Ryan and David Huselid and Alison Huselid and Claude Englebert and Ray Camden
Ben Nadel at Scotch On The Rock (SOTR) 2010 (Amsterdam) with: Terrence Ryan David Huselid Alison Huselid Claude Englebert Ray Camden

CreateGradient() / DrawGradientRect() Added To ImageUtils.cfc

By
Published in Comments (4)

I have added two functions to the ImageUtils.cfc ColdFusion image manipulation component. Building on top of the previously added function, CalculateGradient(), I have now added a function, CreateGradient(), that will create a gradient rectangle on its own canvas and DrawGradientRect(), which will draw a gradient rectangle on top of an existing image.

CreateGradient( FromColor, ToColor, GradientDirection, Width, Height )

This method just creates the gradient canvas. The From and To colors are structs that contain Red, Green, Blue, and Alpha keys for the different color channels. The GradientDirection is the direction of the gradient on the canvas; the possible values for this are TopBottom, BottomTop, LeftRight, and RightLeft. The width and height constitute the dimensions of the gradient rectangle.

DrawGradientRect( Image, X, Y, Width, Height, FromColor, ToColor, GradientDirection )

This method takes a given image and creates the gradient rectangle of the given dimensions (width / height) at the given coordinates (X / Y). The FromColor, ToColor, and GradientDirection are just used to call the CreateGradient() method so the same rules apply to the arguments.

Note: When creating a gradient, we have the option to use the Alpha channel. The Alpha channel goes from 255 which is totally NOT see-through, down to zero, which is totally transparent. Just realize that when you use ColdFusion's ImageSetDrawingTransparency() method, the numbers are in the opposite direction; meaning, a zero alpha which is see-through has a 100 transparency. This makes sense, but the conflicting values might be confusing.

Now that we see what the functions do, let's take a look at a practical example. Here, we are going to read in the image of the Subject and then draw a gradient rectangle over her canvas. Our gradient is going to stay white, but will fade from opaque to transparent:

<!---
	Create to/from colors. In this case we just want to fade
	the alpha channel, not the RGB channels so therefore, the
	To/From colors are both white.
--->
<cfset objFromColor = HextoRGB( "##FFFFFF" ) />
<cfset objToColor = HextoRGB( "##FFFFFF" ) />

<!---
	Add alpha channel. We want the gradient to fade to
	transparent which will have an alpha value of zero.
--->
<cfset objToColor.Alpha = 0 />


<!--- Read in the subject image. --->
<cfimage
	action="read"
	source="./subject.jpg"
	name="objImage"
	/>

<!---
	Apply the gradient rectangle. In this case, we want the
	gradient to cover the entire image, so we are going to
	paste in the 0,0 coordinate and use the entire width and
	height of the image.
--->
<cfset DrawGradientRect(
	objImage,
	0,
	0,
	ImageGetWidth( objImage ),
	ImageGetHeight( objImage ),
	objFromColor,
	objToColor,
	"LeftRight"
	) />

<!--- Draw image. --->
<cfimage
	action="writetobrowser"
	source="#objImage#"
	/>

Notice that after we convert our colors to RGB, we have to set the Alpha channel for our To color to be zero (fade to transparent). Running the above code, we get the following output:

Subject Fading To Transparent White Rectangle

Not bad right? Can anyone guess where we go from here? (Think - image reflection???).

Here is the CreateGradient() ColdFusion image manipulation function that really powers this whole effect:

<cffunction
	name="CreateGradient"
	access="public"
	returntype="any"
	output="false"
	hint="Creates a gradient rectangle to be used with other graphics.">

	<!--- Define arguments. --->
	<cfargument
		name="FromColor"
		type="struct"
		required="true"
		hint="The R,G,B,A struct for the start color of our gradient."
		/>

	<cfargument
		name="ToColor"
		type="struct"
		required="true"
		hint="The R,G,B,A struct for the end color of our gradient."
		/>

	<cfargument
		name="GradientDirection"
		type="string"
		required="true"
		hint="The direction in which to darw the gradient. Possible values are TopBottom, BottomTop, LeftRight, and RightLeft."
		/>

	<cfargument
		name="Width"
		type="numeric"
		required="true"
		hint="The width of the desired rectangle."
		/>

	<cfargument
		name="Height"
		type="numeric"
		required="true"
		hint="The height of the desired rectangle."
		/>

	<!--- Define the local scope. --->
	<cfset var LOCAL = {} />


	<!--- Make sure that we have a valid direciton. --->
	<cfif NOT ListFind(
		"TopBottom,BottomTop,LeftRight,RightLeft",
		ARGUMENTS.GradientDirection
		)>

		<!--- Inavlid gardient, default to TopBottom. --->
		<cfset ARGUMENTS.GradientDirection = "TopBottom" />

	</cfif>


	<!---
		Create a new transparent gradient. It is important
		that it is transparent since the gradient utilizes
		an alpha channel.
	--->
	<cfset LOCAL.Gradient = ImageNew(
		"",
		ARGUMENTS.Width,
		ARGUMENTS.Height,
		"argb",
		""
		) />


	<!---
		In order to figure out what steps that we need to
		create, we need to figure out which direction the
		gradient is going in.
	--->
	<cfswitch expression="#ARGUMENTS.GradientDirection#">

		<!---
			For vertical gradients, use the height to
			define the number of steps.
		--->
		<cfcase value="TopBottom,BottomTop" delimiters=",">
			<cfset LOCAL.StepCount = ARGUMENTS.Height />
		</cfcase>

		<!---
			For horizontal gradients, use the width to
			define the number of steps.
		--->
		<cfcase value="LeftRight,RightLeft" delimiters=",">
			<cfset LOCAL.StepCount = ARGUMENTS.Width />
		</cfcase>

	</cfswitch>


	<!---
		Calculate the gradient using our From and To colors.
		This will give us all the colors in the gradient.
	--->
	<cfset LOCAL.GradientSteps = CalculateGradient(
		ARGUMENTS.FromColor,
		ARGUMENTS.ToColor,
		LOCAL.StepCount
		) />


	<!---
		Now that we have our gradient steps, we can start to
		apply our individual color steps to the blank canvas
		in order to create the gradient rectangle.
	--->

	<!---
		We don't want there to be too much fuziness, so turn
		off antialiasing.
	--->
	<cfset ImageSetAntialiasing( LOCAL.Gradient, "off" ) />

	<!--- Loop over the steps in the gradient. --->
	<cfloop
		index="LOCAL.StepIndex"
		from="1"
		to="#LOCAL.StepCount#"
		step="1">

		<!--- Set the current drawing color. --->
		<cfset ImageSetDrawingColor(
			LOCAL.Gradient,
			(
				LOCAL.GradientSteps[ LOCAL.StepIndex ].Red & "," &
				LOCAL.GradientSteps[ LOCAL.StepIndex ].Green & "," &
				LOCAL.GradientSteps[ LOCAL.StepIndex ].Blue
			)) />

		<!---
			Set the drawing transparency. When doing this,
			we have to be careful as we are not setting the
			opacity, which is actually the opposite value. An
			alpha channel of 255 is totally opaque, but requires
			a tranparency of zero.
		--->
		<cfset ImageSetDrawingTransparency(
			LOCAL.Gradient,
			(100 - (LOCAL.GradientSteps[ LOCAL.StepIndex ].Alpha / 255 * 100))
			) />

		<!---
			When we actually draw the rectangle, we have to take
			into account the direction of the gradient to figure
			out where the individual step gradient will be applied.
		--->
		<cfswitch expression="#ARGUMENTS.GradientDirection#">
			<cfcase value="TopBottom">

				<cfset ImageDrawRect(
					LOCAL.Gradient,
					0,
					(LOCAL.StepIndex - 1),
					ARGUMENTS.Width,
					1,
					true
					) />

			</cfcase>
			<cfcase value="BottomTop">

				<cfset ImageDrawRect(
					LOCAL.Gradient,
					0,
					(ARGUMENTS.Height - LOCAL.StepIndex),
					ARGUMENTS.Width,
					1,
					true
					) />

			</cfcase>
			<cfcase value="LeftRight">

				<cfset ImageDrawRect(
					LOCAL.Gradient,
					(LOCAL.StepIndex - 1),
					0,
					1,
					ARGUMENTS.Height,
					true
					) />

			</cfcase>
			<cfcase value="RightLeft">

				<cfset ImageDrawRect(
					LOCAL.Gradient,
					(ARGUMENTS.Width - LOCAL.StepIndex),
					0,
					1,
					ARGUMENTS.Height,
					true
					) />

			</cfcase>
		</cfswitch>

	</cfloop>


	<!--- Return gradient rectangle. --->
	<cfreturn LOCAL.Gradient />
</cffunction>

Then, we have the DrawGradientRect() ColdFusion image manipulation function. As you can see below, this function really just turns around and calls the CreateGradient() method:

<cffunction
	name="DrawGradientRect"
	access="public"
	returntype="any"
	output="false"
	hint="Takes an image and draws the given gradient rectangle on it.">

	<!--- Define arguments. --->
	<cfargument
		name="Image"
		type="any"
		required="true"
		hint="The ColdFusion image object onto which we are drawing the gradient."
		/>

	<cfargument
		name="X"
		type="numeric"
		required="true"
		hint="The X coordinate at which to start drawing the rectangle."
		/>

	<cfargument
		name="Y"
		type="numeric"
		required="true"
		hint="The Y coordinate at which to start drawing the rectangle."
		/>

	<cfargument
		name="Width"
		type="numeric"
		required="true"
		hint="The width of the desired rectangle."
		/>

	<cfargument
		name="Height"
		type="numeric"
		required="true"
		hint="The height of the desired rectangle."
		/>

	<cfargument
		name="FromColor"
		type="struct"
		required="true"
		hint="The R,G,B,A struct for the start color of our gradient."
		/>

	<cfargument
		name="ToColor"
		type="struct"
		required="true"
		hint="The R,G,B,A struct for the end color of our gradient."
		/>

	<cfargument
		name="GradientDirection"
		type="string"
		required="true"
		hint="The direction in which to darw the gradient. Possible values are TopBottom, BottomTop, LeftRight, and RightLeft."
		/>

	<!--- Define the local scope. --->
	<cfset var LOCAL = {} />

	<!--- Create the gradient rectangle. --->
	<cfset LOCAL.Gradient = CreateGradient(
		ARGUMENTS.FromColor,
		ARGUMENTS.ToColor,
		ARGUMENTS.GradientDirection,
		ARGUMENTS.Width,
		ARGUMENTS.Height
		) />

	<!--- Paste the gradient onto the image. --->
	<cfset ImagePaste(
		ARGUMENTS.Image,
		LOCAL.Gradient,
		ARGUMENTS.X,
		ARGUMENTS.Y
		) />

	<!--- Return the updated image. --->
	<cfreturn ARGUMENTS.Image />
</cffunction>

There's a few reasons that we are splitting this functionality up into two different methods. For starters, it saves you from having to call the CreateGradient() method and translate the logic. But mostly, we want to create a gradient method that is independent of the target image in order to allow it to be reused by various other methods. As I have hinted above, we can now start to use this feature and even the DrawGradientRect() method to create things like image reflection effects. The more we can split things up into smaller, more cohesive pieces of functionality, the more we can start to reuse them.

Want to use code from this post? Check out the license.

Reader Comments

21 Comments

Hey Ben,

Was there a particular reason that you have the "passthrough" parameters for DrawGradientRect in a different order than they are in CreateGradient? It seems to me to make the order of parameters the same would make them easier to remember (as well as potentially copy and paste when moving from a CreateGradient to DrawGradientRect as your needs change). Unless of course you're using an application that gives you the code hints for CFC methods. (Speaking of that...Ben, write me and I might be able to share a a little toy to play with in Dreamweaver)

Also, it might be good to add a Gradient object/struct to the mix, and that way you can pass it around into these types of functions, especially if you're going to do "reflector-ization" on multiple images as some sort of batch processing.

15,841 Comments

@Danilo,

It's funny that you bring that up because I definitely had them in the same order on the first run and then went back and changed them. I think part of me wanted to emphasize the "gradient" part of the CreateGradient() method. I almost wanted to *stop* the copy-paste mentality of the arguments - I wanted people to think about what the context of the method was, not just that one turns around and calls another.

I am, however, not really sold one way or the other. And, actually, now that I think of it, I believe that the rest of ColdFusion's image manipulation functions have the coordinates and dimensions first. So, I guess in that respect, it would be more consistent to have width / height of the gradient first and then the colors.

As far as the gradient struct, I am not 100% sure what you are saying. I think you mean that we would have to recalculate the gradient for each image, and if we are doing a lot of images in a row, that could be a lot of work?? If that is what you mean, I could certainly see the ability to call the CalculateGradient() method to return the array, and then some sort of ApplyGradient() method that uses the same array of color indexes to an image.

21 Comments

Ben,

My initial thought was that you could pass the DrawGradientRect a Gradient struct with the properties that are needed by CreateGradient DrawGradientRect, but then I was also thinking about the calculated set of color values that could then be used multiple times. As I've not looked at the implementation of these functions, it could be useful whether they are simple pixel color values for the entire rectangle, or if an actual gradient is specified without needing to set each pixel individually. More useful if you have to set each pixel's color value within the rectangle.

So anyway, it could be as simple as a struct that has just the properties needed, or as complex as the fully calculated pixel values for the gradiented (?) rectangle.

So now that you have linear gradients, how about other shapes as well? If you're going down that route, then you may want to have a gradient struct with all the parameters and then pass in a type attribute to the CreateGradient so that it can handle linear, circular, and other types of gradients (whatever they may be).

15,841 Comments

@Danilo,

I see more of what you are saying. I think for the moment, I don't want to lose steam. However, I believe that that is something that could be refactored in shortly. Right now, let me see where it takes me.

Good suggestions.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel