<?php
// $Id$
/**
* @file rgbapng.module
* @desc This module provides callbacks that generate transparent PNG for CSS backgrounds.
*
* DEPENDENCIES imageapi, imageapi_gd
* USAGE in your theme stylesheets, use rules like this and in this order:
* selector {
* background: url(rgbapng/FF000040);
* background: rgba(255,0,0,0.5);
* }
* You still need some PNG hack for <IE7 to display PNGs with alpha transparency
* @link http://leaverou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/
*/
/**
* Implementation of hook_menu()
*/
function rgbapng_menu() {
$items = array();
// If you use system_theme_data() to list themes, you can't ever enable
// new themes at admin/build/themes. It's one of the ways Drupal is weird.
$result = db_query("SELECT filename FROM {system} WHERE status = 1 AND type = 'theme'");
while ($filename = db_result($result)) {
$dir = dirname($filename);
$items[$dir .'/rgbapng/%'] = array(
'title' => 'RGBa PNG',
'page callback' => 'rgbapng_image',
'page arguments' => array(count(split('/', $dir)) + 1),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
}
return $items;
}
/*
* feature taken from
* http://leaverou.me/2009/02/bulletproof-cross-browser-rgba-backgrounds/
*/
function rgbapng_image($hex) {
$file = file_create_path() .'/rgbapng/'. $hex .'.png';
$dir = dirname($file);
if (!file_check_directory($dir, FILE_CREATE_DIRECTORY) && !mkdir($dir, 0775, true)) {
watchdog('rgbapng', 'Failed to create rgbapng directory: %dir', array('%dir' => $dir), WATCHDOG_ERROR);
return false;
}
if (is_file($file)) {
rgbapng_transfer($file);
}
$lockfile = file_directory_temp() .'/rgbapng' . basename($file);
if (file_exists($lockfile)) {
watchdog('rgbapng', 'RGBa PNG already generating: %dst, Lock file: %tmp.', array('%dst' => $file, '%tmp' => $lockfile), WATCHDOG_NOTICE);
// 307 Temporary Redirect, to myself. Lets hope the image is done next time around.
header('Location: '. request_uri(), TRUE, 307);
exit;
}
touch($lockfile);
// register the shtdown function to clean up lock files. by the time shutdown
// functions are being called the cwd has changed from document root, to
// server root so absolute paths must be used for files in shutdown functions.
register_shutdown_function('file_delete', realpath($lockfile));
list($r, $g, $b, $a) = imageapi_hex2rgba($hex);
$image = new stdClass();
$image->toolkit = 'imageapi_gd';
$image->info = array(
'width' => 1,
'height' => 1,
'extension' => 'png',
'mime_type' => 'image/png',
);
$image->resource = @imagecreatetruecolor(1,1)
or die('Cannot Initialize new GD image stream');
imagealphablending($image->resource, FALSE);
imagesavealpha($image->resource, TRUE);
// Allocate our requested color
$color = imagecolorallocatealpha($image->resource, $r, $g, $b, $a);
// Fill the image with it
imagefill($image->resource, 0, 0, $color);
imageapi_image_close($image, $file);
if (file_exists($file)) {
rgbapng_transfer($file);
}
// Generate an error if image could not generate.
watchdog('rgbapng', 'Failed generating an image from %hex using RGBa PNG.', array('%hex' => $hex), WATCHDOG_ERROR);
header("HTTP/1.0 500 Internal Server Error");
exit;
}
/**
* STOLEN from imagecache_transfer
* helper function to transfer files from rgbapng. Determines mime type and sets a last modified header.
* @param $path path to file to be transferred.
* @return <exit>
*/
function rgbapng_transfer($path) {
$size = getimagesize($path);
$headers = array('Content-Type: '. mime_header_encode($size['mime']));
if ($fileinfo = stat($path)) {
$headers[] = 'Content-Length: '. $fileinfo[7];
_rgbapng_cache_set_cache_headers($fileinfo, $headers);
}
file_transfer($path, $headers);
exit;
}
/**
* STOLEN from _imagecache_cache_set_cache_headers
* Set file headers that handle "If-Modified-Since" correctly for the
* given fileinfo. Most code has been taken from drupal_page_cache_header().
*/
function _rgbapng_cache_set_cache_headers($fileinfo, &$headers) {
// Set default values:
$last_modified = gmdate('D, d M Y H:i:s', $fileinfo[9]) .' GMT';
$etag = '"'. md5($last_modified) .'"';
// See if the client has provided the required HTTP headers:
$if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE'])
: false;
$if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH'])
? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
: false;
if ($if_modified_since && $if_none_match
&& $if_none_match == $etag // etag must match
&& $if_modified_since == $last_modified) { // if-modified-since must match
header('HTTP/1.1 304 Not Modified');
// All 304 responses must send an etag if the 200 response
// for the same object contained an etag
header('Etag: '. $etag);
// We must also set Last-Modified again, so that we overwrite Drupal's
// default Last-Modified header with the right one
header('Last-Modified: '. $last_modified);
exit;
}
// Send appropriate response:
$headers[] = 'Last-Modified: '. $last_modified;
$headers[] = 'ETag: '. $etag;
}