How To Make A Transparent Image With Dynamic Color Layers

This is a new one hot off the press. I’m excited because this is the first actual product I’ve come up with that I can immediately put on my blog.

Right now, I’m working on a site that will eventually use brand-based templates. The client hopes to eventually sell to other people, who can then color up the site to match their particular branding and colors. For the most part, this is easy. CSS covers most of the items that would be colored but images cannot…. The images on this site need to not only have a changing color, but there are shadows and other elements to the images that need to be branded as well.

This is the original image:

Here’s an example of what I’m talking about:

The process for this is pretty easy. To start, you need to break your source image into separate, one-color (black) layers; one for each different color grade you plan to use. From there, the code will work to overwrite your one-color channel with the desired color and grade. Take our source image; when you break it apart, you get the following layer images:

Another thing to note: over time experimenting with this code, I’ve found it best to start with a image larger than you intend to use. For the most part, this seems to help with pixelation in merging the layers.

Our source image cleanly breaks into three layers: the border, the background, and the quotes. Now, we want the original color to be assigned to the border layer, a solid white to the quotes, and somewhere in between for the background.

Use whatever method you would like to measure out the differences in your original source image. For ours, I found the color to be about 35% lighter than the border. So, for any new image, we know that the background will be that same grade lighter, whatever the color. After a little Google searching I found the below function (originally here) to help translate colors for me. Simply provide it a six-character hex code (eg. #FF0000) and a factor, and the code will return you a new six-character hex that represents a color that is that much closer to white.

function hexLighter($hex, $factor = 30) {
	$new_hex = '';
	
	$base['R'] = hexdec($hex{0}.$hex{1});
	$base['G'] = hexdec($hex{2}.$hex{3});
	$base['B'] = hexdec($hex{4}.$hex{5});
	
	foreach ($base as $k => $v) {
		$amount = 255 - $v;
		$amount = $amount / 100;
		$amount = round($amount * $factor);
		$new_decimal = $v + $amount;
		
		$new_hex_component = dechex($new_decimal);
		
		$new_hex .= sprintf('%02.2s', $new_hex_component);
	}
	
	return $new_hex;         
}

Next, we set up our images to work with. Using the PHP GD image library, we need to create two images: one to be our final composite, and one to help with the merging (explained more later).

// Desired final size of image
$n_width = 50;
$n_height = 50;

// Actual size of source images
$width = 125;
$height = 125;

$image = 	imagecreatetruecolor($width, $height);
			imagesavealpha($image, true);
			imagealphablending($image, false);

$n_image = 	imagecreatetruecolor($n_width, $n_height);
			imagesavealpha($n_image, true);
			imagealphablending($n_image, false);

$black = imagecolorallocate($image, 0, 0, 0);
$transparent = imagecolorallocatealpha($image, 255, 255, 255, 127);
	
imagefilledrectangle($image, 0, 0, $width, $height, $transparent);

There’s a lot that goes into setting up a transparent image, as you can see. First you have to declare the new object (lines 9, 12). After declaring those images, you have to tell the GD engine to use the alpha channel, as it does not by defauly (lines 10, 13). Also, since we will be merging images, you have to temporarily disable GD’s alpha blending (lines 11, 14). Then, you allocate/define the colors you want to work with (lines 17, 18). Finally, you set the canvas by laying a layer of “transparent” color first before anything else (line 20). This set the stage for all our other work.

Next, we move to our created layers. Remember, they already need to have a transparent element to them, so stick to .gif or .png images for these. In the code, all the coloring is automated and fed by an array of parameters:

$layers[] = array( 'src' => 'layer01.gif', 'level' => 0 );	// Border
$layers[] = array( 'src' => 'layer02.gif', 'level' => 35 ); 	// Background
$layers[] = array( 'src' => 'layer03.gif', 'level' => 100 );	// White Quotes

Each array represents a separate image layer to be stiched. The src field corresponds to the filename of the image (relative to this script), and the level field represents the grade of color change you want. 0 represents the exact color provided and 100 is pure white.

Now for the really technical part. For each different layer provided, the script will scan it pixel-by-pixel on the x/y axes. For every transparent pixel it finds, the script does nothing. But, for every black pixel it finds, the script will replace it with the color code representing the provided color and the grade level for that layer. Simple enough, right? Here’s the code:

foreach ($layers as $idx => $layer) { 
	$img = imagecreatefromgif( $layer['src'] );
	$processed = imagecreatetruecolor($width, $height);
	
	imagesavealpha($processed, true);
	imagealphablending($processed, false);
	
	imagefilledrectangle($processed, 0, 0, $width, $height, $transparent);
	
	$color = hexLighter( $_GET['color'], $layer['level'] );
	$color = imagecolorallocate($image,
		hexdec( $color{0} . $color{1} ),
		hexdec( $color{2} . $color{3} ),
		hexdec( $color{4} . $color{5} )
	);
	
	for ($x = 0; $x < $width; $x++)
		for ($y = 0; $y < $height; $y++)			
			if ($black === imagecolorat($img, $x, $y))
				imagesetpixel($processed, $x, $y, $color);
	
	imagecolortransparent($processed, $transparent);
	imagealphablending($processed, true);
	
	array_push($layers_processed, $processed);
	
	imagedestroy( $img );
}

The code will loop through all provided source images, process them, and then add them to another, processed, array of images. First, we have to read the source image into memory (line 2). Then, we create a new image to hold all the processed information (lines 3, 5, 6, and 8). After that, we finally get to use our hexLighter function to calculate out the color we will be using, then we translate it to GD (lines 10, 11).

Now we have our stage set for the transfer/processing of color. Think of this next part like using a pantograph on your source layer. You scan the x and y axes of your source image, and for every black color you find, you insert a "color"-ed point at the same x/y coordinated on your new image (lines 17-20). Finally, push the image into the "processed" array, save a little memory by using imagedestroy on the source image you don't need anymore (line 27), and move on to the next layer.

With the technical complexities out of the way, we move to the procedural complexities. GD is a very particular library to work with; if you don't get your code exactly right, call all the right functions, and all in the right order, the image will not work. That said, this is the reason why we create two images to begin with.

foreach ($layers_processed as $processed) {
	imagecopymerge($image, $processed, 0, 0, 0, 0, $width, $height, 100);
	
	imagedestroy( $processed );
}

imagealphablending($image, true);

imagecopyresampled($n_image, $image, 0, 0, 0, 0, $n_width, $n_height, $width, $height);

imagealphablending($n_image, true);

The first, larger image you declared all the way back in the beginning is now used to build a composite of your newly created layers. The second image is then used for re-sizing the composite down to your desired size. It may seem like an extra step, but in order to keep the transparency channels in every layer and produce a true transparent image. Which brings me to the last step. Way back when we created the images, I mentioned that the alpha blending needed to be turned off. Now that we're done, it's time to turn it back on (line 11).

Finally, we serve up the image. Since this is intended to be the source of an img tag, we could just serve it back using either imagepng() or imagegif(), depending on taste. Remember to adjust your header depending on your choice.

// Towards the top of your script
if (file_exists( "cache/{$_GET['color']}.png" )) {
	header( 'Content-Type: image/png' );
	readfile( "cache/{$_GET['color']}.png" );
	
	exit(0);
}

// The final save/return of a newly processed image
header( 'Content-Type: image/png' );
imagepng( $n_image, "cache/{$_GET['color']}.png" );
imagepng( $n_image );

I decided to take it a step further and build in a simple file caching system to first check if the file exists before proceeding (lines 2-7). If no image already exists, the script runs through, and saves a copy in the cache (line 11) before returning the new image (line 12). Ideally, the client-user will call any newly-generated image long before any end-user does, leaving the end-user with a fraction of the processing time.

Here's the code all together:

function hexLighter($hex, $factor = 30) {
	$new_hex = '';
	
	$base['R'] = hexdec($hex{0}.$hex{1});
	$base['G'] = hexdec($hex{2}.$hex{3});
	$base['B'] = hexdec($hex{4}.$hex{5});
	
	foreach ($base as $k => $v) {
		$amount = 255 - $v;
		$amount = $amount / 100;
		$amount = round($amount * $factor);
		$new_decimal = $v + $amount;
		
		$new_hex_component = dechex($new_decimal);
		
		$new_hex .= sprintf('%02.2s', $new_hex_component);
	}
	
	return $new_hex;         
}

// Sanitize/Validate provided color variable
if (!isset($_GET['color']) || strlen($_GET['color']) != 6) {
	header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request', true, 400);
	
	exit(0);
}

if (file_exists( "cache/{$_GET['color']}.png" )) {
	header( 'Content-Type: image/png' );
	readfile( "cache/{$_GET['color']}.png" );
	
	exit(0);
}

// Desired final size of image
$n_width = 50;
$n_height = 50;

// Actual size of source images
$width = 125;
$height = 125;

$image = 	imagecreatetruecolor($width, $height);
			imagesavealpha($image, true);
			imagealphablending($image, false);

$n_image = 	imagecreatetruecolor($n_width, $n_height);
			imagesavealpha($n_image, true);
			imagealphablending($n_image, false);

$black = imagecolorallocate($image, 0, 0, 0);
$transparent = imagecolorallocatealpha($image, 255, 255, 255, 127);
	
imagefilledrectangle($image, 0, 0, $width, $height, $transparent);

$layers = array();
$layers_processed = array();

$layers[] = array( 'src' => 'layer01.gif', 'level' => 0 );	// Border
$layers[] = array( 'src' => 'layer02.gif', 'level' => 35 ); 	// Background
$layers[] = array( 'src' => 'layer03.gif', 'level' => 100 );	// White Quotes

foreach ($layers as $idx => $layer) { 
	$img = imagecreatefromgif( $layer['src'] );
	$processed = imagecreatetruecolor($width, $height);
	
	imagesavealpha($processed, true);
	imagealphablending($processed, false);
	
	imagefilledrectangle($processed, 0, 0, $width, $height, $transparent);
	
	$color = hexLighter( $_GET['color'], $layer['level'] );
	$color = imagecolorallocate($image,
		hexdec( $color{0} . $color{1} ),
		hexdec( $color{2} . $color{3} ),
		hexdec( $color{4} . $color{5} )
	);
	
	for ($x = 0; $x < $width; $x++)
		for ($y = 0; $y < $height; $y++)			
			if ($black === imagecolorat($img, $x, $y))
				imagesetpixel($processed, $x, $y, $color);
	
	imagecolortransparent($processed, $transparent);
	imagealphablending($processed, true);
	
	array_push($layers_processed, $processed);
	
	imagedestroy( $img );
}

foreach ($layers_processed as $processed) {
	imagecopymerge($image, $processed, 0, 0, 0, 0, $width, $height, 100);
	
	imagedestroy( $processed );
}

imagealphablending($image, true);

imagecopyresampled($n_image, $image, 0, 0, 0, 0, $n_width, $n_height, $width, $height);

imagealphablending($n_image, true);

header( 'Content-Type: image/png' );
imagepng( $n_image, "cache/{$_GET['color']}.png" );
imagepng( $n_image );

// Free up memory
imagedestroy( $n_image );
imagedestroy( $image );

With this script, you can provide any hex code and get a themed image returned. So a url of /image.php?color=223455 gets you:

And to use this in your markup:


Hope you enjoy and, if you have any ideas for improvements, please let me know!

Posted in PHP | Tagged , | 1 Comment

Backwards-Compatible CSS Gradients

I’m a big fan of content over images in a website. That’s not to say that all images or graphic-based content is bad; I just believe much of what a designer wants for a website, and what is actually required to make that happen, are two completely different things. Many years ago, I was a child of the “table-based” design principals championed by slicing-capable programs like Fireworks and (unfortunately) GoLive.

Graphic-based “table websites” load slowly, have the sliced-up feel to them, and frankly just look ugly. It’s taken a long time to break myself of the bad habits perpetuated by programs like these, and to see that I could build sites from a rendering using only a fraction of the images I would have used to. Over time, I will add more and more of the techniques I’ve learned to this, and today I want to focus on one of the most important: gradients.

Gradients are everywhere, in every design you’ve probably ever seen. Because of these, it is sometimes harder to not use images, but not impossible. Recent advances in browsers capabilities have allowed for CSS gradients to become prominent features in a site’s design. Of course, not everybody has upgraded to state-of-the-art browsers, so we will have to use a variation of “Fallback Declarations” again in order to tastefully degrade your site to the capabilities of older browsers. Here’s the code:

Markup:

CSS:

#gradient_box {
	/* Simple visual properties */
	height: 100px;
	width: 100px;
	border: solid 2px black;
	
	/* Gradient code */
	background: #FF0000;
	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FF0000', endColorstr='#00FF00');
	background: -webkit-linear-gradient(top, #FF0000, #00FF00);
	background: -moz-linear-gradient(top, #FF0000, #00FF00);
	background: -o-linear-gradient(top, #FF0000, #00FF00);
}

Here’s an example of how it will look.

The code here is simple. First, you set the background color, establishing a base-level color for the element. After that, you add the IE-specific declaration filter. For this tutorial, I chose the “gradient” filter, but a full list of the different available filters for IE can be found here. Then, you add the vendor-specific prefixes (webkit, mozilla, and opera) and you’re done. Very simple!

One quick note, while these features are not fully supported (waiting on you IE), you can add more colors to your gradient by adding more color codes to the end of the list. linear-gradient(top, #FF0000, #00FF00) becomes linear-gradient(top, #FF0000, #00FF00, #0000FF) and so on. You can also use transparency by using rgba values, assuming your browser is in the 21st century, of course.

Posted in CSS | Tagged , , , | Leave a comment

Javascript-Free Image Desaturate

While designing my portfolio page, I wanted to bring in a simple desaturate effect to my images. After a little searching, I found that this seemed to be a very easy task, so long as I was using some kind of Javascript framework. This seemed to be a bit overkill for my purposes so I set to trying to find a pure (no script) way to accomplish this task. After some research, I found my method: CSS Transitions.

Over the last year or so, the capabilities of web browsers have risen to accommodate the basic use of CSS Transitions in every up-to-date browser. These include size, position, and for the purposes of this tutorial style.

For this tutorial, you need 4 things: some markup, a little CSS, and two images, one a de-saturated version of the other. To see a demonstration, check out my portfolio.

Images:

Markup:

CSS:

div.image {
	position: relative;
	width: auto;
	height: 100px;
	float: left;
	border: solid 2px #333;
}
 
div.image img {
	position: absolute;
	display: block;
	top: 0;
	left: 0;
}
 
div.image:hover img.bw {
	opacity: 0;
	-webkit-transition: opacity .1s linear .1s;
	-moz-transition: opacity .1s linear .1s;
	-o-transition: opacity .1s linear .1s;
	transition: opacity .1s linear .1s;
	visibility : hidden;
}
 
div.image img.bw {
	z-index: 21;
	-webkit-transition: opacity .1s linear .1s;
	-moz-transition: opacity .1s linear .1s;
	-o-transition: opacity .1s linear .1s;
	transition: opacity .1s linear .1s;
	visibility : visible;
}
 
div.image img.color {
	z-index: 20;
}

One thing I should explain at this point is that the code in this tutorial does not really transition “between” the two images, but rather creates the illusion of a between transition. We accomplish this by positioning the two given images on top of one another and then adjusting the opacity property of the “top” black-and-white image, revealing the “bottom” colored image. In order to better explain this, let’s break everything down to two separate problems: we need to position the images on top of one another, and we need to be able to transition between the two.

First, you can position two items on top of each other using a little-known CSS trick. Normally, when using the code position: absolute in CSS, it causes the element to jump out of the flow of a website and set its positioning based on the origin point (0,0) of the browser. This usually is the top-left of a browser, causing all kinds of problems for center-oriented or fluid-width websites. Now, with this trick, you can fool the browser and change the origin point for any object.

Starting with the container (Line 1, markup and css), set your CSS to position: relative, then for any children of the container, you set position: absolute. This may seem counter-productive, having differing style declarations, but what this does is actually reset that origin point, making it the top-left of that element, rather than top-left of the browser. From there, a left: 0 and a top: 0 for both items will place both items in the same place, on top of each other. Then, to choose which image would be on top, you set your z-index value. Here, I chose black-and-white to be over the color, by setting the z-index value for color lower than black-and-white.

Now that we have position down, let’s turn to the transition. Because this is not a fully supported feature across all browsers, some care and planning usually has to go into any CSS declaration. First, we want this transition to trigger on hovering (or “rolling”) over the image and there are two images for which to account, so we need to look to the parent element for our trigger. The style selector at line 16 makes use of the :hover pseudo-selector. This tells the browser to use the declarations contained within only if the user have hovered over that element. Since we know this element will contain the two images, we now have our trigger selected out. From there, we add our declarations.

If you want your style to “work” in as many environments as possible, you have to use what I call fallback declarations. Think of Fallback Declarations as synonyms of a normal declaration; they only go into effect if the first declaration is read as invalid. These types of declarations are usually (but not always) a type of declaration called a vendor prefix declaration. As a browser parses CSS declarations in a given style, it moves from top to bottom looking for styles that it recognizes.

For our purposes, let’s look at the style starting at line 16. The transition fallback begins on the second declaration (line 18) with a series of vendor prefix declarations. As the browser parses the declarations, it will either grab its specified declaration, or fall to the next line. After the vendor prefixes, we have the actual CSS3-standard transition declaration. Eventually, the 3 lines of vendor-specific redundancy will not be needed, but for now we must allow for browsers who have yet adopted CSS3. The final line is the “ancient-browser” fix, not even bothering with the opacity property, and opting to either just be visible or not.

From here, we’ve only transitioned to one image, but not back the other way. This is where we set up a similar non-pseudo style block (line 25) that the browser will use for the hover (or “roll”) out event. The only differences between the two style blocks is that one (the hover block) sets opacity of the image to 0% while the other (the non-hover block) returns opacity to 100%. The work’s already done, you just need a little copy/paste and some small changes and you have a JavaScript-free image desaturate effect.

Posted in Web Design | Tagged , , , | 2 Comments