Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Generating Color Swatches With GraphicsMagick And Lucee CFML 5.3.7.47

By Ben Nadel on
Tags: ColdFusion

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!



Reader 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.

Reply to this Comment

@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.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.