Skip to main content
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Laura Springer
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Laura Springer

Generating Color Swatches With GraphicsMagick And Lucee CFML 5.3.7.47

By
Published in Comments (2)

At InVision, I've been playing around with an export feature for Boards. Unlike Prototypes - which only aggregate a single "type" of data (screens) - a Board can include files, notes, and color swatches. Since color swatches are just HEX values, I thought it would be fun to try and materialize those HEX values as actual images. To explore this concept, I'm using GraphicsMagick and Lucee CFML 5.3.7.47.

The idea here is rather simple: I want to take the HEX value and generate a blank canvas using said HEX value as the background color. Then, I want to add a text-label for the HEX value and position in the bottom-left corner of the canvas.

To generate the solid-color canvas, we can use the pseudo-image format, xc. This stands for "Constant image uniform color" (which means nothing to me, but is what the documentation says). So, to create a 400x400 canvas with a white background (#ffffff), we would do this:

convert -size 400x400 xc:#ffffff

Adding the label FFFFFF to the bottom-left corner is a bit trickier and required brute-force trial-and-error in terms of sizing and positioning. Essentially, I just kept trying different {x,y} coordinates and then evaluating the results visually.

With regard to the Font family used in the text operation, I get a little confused. I am not entirely sure which fonts the GraphicMagick binary has access to. But, it seems that you can link directly to a font file. So, I went to Google Font and downloaded Roboto Mono, an attractive monospace font.

Ultimately, here's the ColdFusion code that I came up with - it performs a parallel mapping of six hex colors which results in six color swatch image files:

<cfscript>

	hexValues = [ "E63946", "F1FAEE", "A8DADC", "457B9D", "1D3557", "001219" ];

	swatches = hexValues.map(
		( hexValue ) => {

			var imageFile = "./images/#hexValue#.png";

			gm([
				"convert",

				// Since we're not manipulating an existing image, we have to explicitly
				// define the size of the new image that we're creating.
				"-size 400x400",

				// The foundation of our swatch image will be a solid-colored background.
				// For this, we can use the "XC" (Constant image uniform color) pseudo-
				// image formats. XC will paint the given solid color onto the canvas.
				"xc:###hexValue#",

				// Draw the box for our HEX color label in the bottom-left corner of the
				// canvas.
				"-fill ##ffffff",
				"-draw 'roundRectangle 3,370 86,397 1,1'",

				// Draw the HEX color onto the label. To choose a specific font, we can
				// link directly to the True Type Font file (which I downloaded for free
				// from Google Fonts).
				"-font #quote( expandPath( './Roboto Mono/RobotoMono-VariableFont_wght.ttf' ) )#",
				"-fill ##262626",
				"-pointsize 18",
				"-draw 'text 12,391 #quote( hexValue )#'",

				// Output the swatch image file using the PNG32 writer.
				"PNG32:#quote( expandPath( imageFile ) )#"
			]);

			return({
				hex: hexValue,
				imageUrl: imageFile
			});

		},
		// Parallel iteration.
		true,
		// Maximum number of threads.
		3
	);

	```
	<cfoutput>
		<cfloop value="swatch" array="#swatches#">
			<img
				src="#encodeForHtmlAttribute( swatch.imageUrl )#"
				alt="Color swatch for ###encodeForHtmlAttribute( swatch.hex )#."
				title="Swatch: ###encodeForHtmlAttribute( swatch.hex )#"
			/>
		</cfloop>
	</cfoutput>
	```

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I execute the given options against the GM (GraphicsMagick) command-line tool. If
	* there is an error, the error is dumped-out and the processing is halted. If there
	* is no error, the standard-output is returned.
	* 
	* NOTE: Options are flattened using a space (" ").
	*/
	public string function gm(
		required array options,
		numeric timeout = 5
		) {

		execute
			name = "gm"
			arguments = options.toList( " " )
			variable = "local.successResult"
			errorVariable = "local.errorResult"
			timeout = timeout
		;

		// If the error variable has been populated, it means the CFExecute tag ran into
		// an error - let's dump-it-out and halt processing.
		if ( len( errorResult ?: "" ) ) {

			dump( errorResult );
			abort;

		}

		return( successResult ?: "" );

	}


	/**
	* A simple utility function that wraps the given value in double-quotes. This makes
	* the command-line arguments a bit easier to read.
	* 
	* NOTE: I would have loved to figure out how to ESCAPE embedded quotes within the
	* value; but that appears to be a rather complicated issue command-line issue that I
	* don't know how to solve. Thankfully, none of my inputs have quotes in them.
	*/
	public string function quote( required string value ) {

		return( '"#value#"' );

	}

</cfscript>

As I mentioned above, the numbers that I'm using in the various -draw operations were all just trial-and-error. I'd run the GraphicsMagick command and then make a judgement call on what the end-result looked like.

By using the parallel iteration, we're able to execute the GraphicsMagick CLI in parallel, taking advantage of the machine's underlying resources. Though, drawing a blank canvas and some text is insanely fast to being with. And, when we run this ColdFusion code, we end up with the following browser output:

Well, that's just very pleasant looking, if I don't say so myself. I'm really enjoying working with GraphicsMagick. It can be super frustrating trying to figure out the right combination of commands. But, once you get it working, it runs super fast!

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

Reader Comments

89 Comments

You can use pre-existing images with GraphicMagick's Montage utility to automatically generate contact sheets.
http://www.graphicsmagick.org/montage.html

gm.exe montage -geometry 120x120 -tile 3x2 C:\1.png C:\2.png C:\3.png C:\4.png C:\5.png C:\6.png c:\output.jpg

Here's the syntax that I used to add a shadow and file-based titles to the contact sheet:

gm.exe montage +adjoin -compress JPEG +frame +shadow -font Arial -pointsize 15 -tile 4x1 -label %t -geometry 350x350+4+8> -title "Montage with Titles" C:\1.png C:\2.png C:\3.png C:\4.png C:\5.png C:\6.png c:\output.jpg

Another swatch generation option would be to generate HTML and use wkhtmltoimage. This approach would enable you to use custom webfonts, round corners, add shadpe/text shadows, transparent overlays. If you use wkhtmltopdf to instead generate a PDF, you could have each color link to an online color wheel to explore and identify complementary, monochromatic, analogous, triadic & tetradic colors.

15,919 Comments

@James,

I've always been curious to try the montage functionality. I haven't needed it, really, in the past; but, this would be a nice use-case. Especially cause I could create individual swatches and then create the one overall swatch.

Re: WKHtmlToImage - honestly, I've been dying to use something like that since you pointed it out a few years ago. I just fear that it would be a pretty big lift to get it by the Security team (which is already stretched very thing). But, maybe I'll start putting some feelers out there.

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