'fieldset',
'#title' => t('Color scheme'),
'#weight' => -1,
'#attributes' => array('id' => 'color_scheme_form'),
'#theme' => 'color_scheme_form',
);
$form['color'] += color_scheme_form(arg(4));
$form['#submit']['color_scheme_form_submit'] = array();
}
// Use the generated screenshot in the theme list
if ($form_id == 'system_theme_select_form' || $form_id == 'system_themes') {
$themes = list_themes();
foreach (element_children($form) as $theme) {
if ($screenshot = variable_get('color_'. $theme .'_screenshot', NULL)) {
if (isset($form[$theme]['screenshot'])) {
$form[$theme]['screenshot']['#value'] = theme('image', $screenshot, '', '', array('class' => 'screenshot'), FALSE);
}
}
}
}
}
/**
* Callback for the theme to alter the resources used.
*/
function _color_page_alter(&$vars) {
global $theme_key;
// Override stylesheet
$path = variable_get('color_'. $theme_key .'_stylesheet', NULL);
if ($path) {
$vars['css']['theme'] = array($path => array('path' => $path, 'media' => 'all'));
$vars['styles'] = drupal_get_css($vars['css']);
}
// Override logo
$logo = variable_get('color_'. $theme_key .'_logo', NULL);
if ($logo && $vars['logo'] && preg_match('!'. $theme_key .'/logo.png$!', $vars['logo'])) {
$vars['logo'] = base_path() . $logo;
}
}
/**
* Retrieve the color.module info for a particular theme.
*/
function color_get_info($theme) {
$path = drupal_get_path('theme', $theme);
$file = $path .'/color/color.inc';
if ($path && file_exists($file)) {
include $file;
return $info;
}
}
/**
* Helper function to retrieve the color palette for a particular theme.
*/
function color_get_palette($theme, $default = false) {
// Fetch and expand default palette
$fields = array('base', 'link', 'top', 'bottom', 'text');
$info = color_get_info($theme);
$keys = array_keys($info['schemes']);
foreach (explode(',', array_shift($keys)) as $k => $scheme) {
$palette[$fields[$k]] = $scheme;
}
// Load variable
return $default ? $palette : variable_get('color_'. $theme .'_palette', $palette);
}
/**
* Form callback. Returns the configuration form.
*/
function color_scheme_form($theme) {
$base = drupal_get_path('module', 'color');
$info = color_get_info($theme);
// Add Farbtastic color picker
drupal_add_css('misc/farbtastic/farbtastic.css');
drupal_add_js('misc/farbtastic/farbtastic.js');
// Add custom CSS/JS
drupal_add_css($base .'/color.css', 'module');
drupal_add_js($base .'/color.js');
drupal_add_js(array('color' => array(
'reference' => color_get_palette($theme, true)
)), 'setting');
// See if we're using a predefined scheme
$current = implode(',', variable_get('color_'. $theme .'_palette', array()));
// Note: we use the original theme when the default scheme is chosen.
$current = isset($info['schemes'][$current]) ? $current : ($current == '' ? reset($info['schemes']) : '');
// Add scheme selector
$info['schemes'][''] = t('Custom');
$form['scheme'] = array(
'#type' => 'select',
'#title' => t('Color set'),
'#options' => $info['schemes'],
'#default_value' => $current,
);
// Add palette fields
$palette = color_get_palette($theme);
$names = array(
'base' => t('Base color'),
'link' => t('Link color'),
'top' => t('Header top'),
'bottom' => t('Header bottom'),
'text' => t('Text color')
);
$form['palette']['#tree'] = true;
foreach ($palette as $name => $value) {
$form['palette'][$name] = array(
'#type' => 'textfield',
'#title' => $names[$name],
'#default_value' => $value,
'#size' => 8,
);
}
$form['theme'] = array('#type' => 'value', '#value' => arg(4));
$form['info'] = array('#type' => 'value', '#value' => $info);
return $form;
}
/**
* Theme color form.
*/
function theme_color_scheme_form($form) {
// Include stylesheet
$theme = $form['theme']['#value'];
$info = $form['info']['#value'];
$path = drupal_get_path('theme', $theme) .'/';
drupal_add_css($path . $info['preview_css']);
// Wrapper
$output .= '
';
$output .= '';
return $output;
}
/**
* Submit handler for color change form.
*/
function color_scheme_form_submit($form_id, $values) {
// Get theme coloring info
if (!isset($values['info'])) {
return;
}
$theme = $values['theme'];
$info = $values['info'];
// Resolve palette
$palette = $values['palette'];
if ($values['scheme'] != '') {
$scheme = explode(',', $values['scheme']);
foreach ($palette as $k => $color) {
$palette[$k] = array_shift($scheme);
}
}
// Delete old files
foreach (variable_get('color_'. $theme .'_files', array()) as $file) {
@unlink($file);
}
if ($file = dirname($file)) {
@rmdir($file);
}
// Don't render the default colorscheme, use the standard theme instead.
if (implode(',', color_get_palette($theme, true)) == implode(',', $palette)
|| $values['op'] == t('Reset to defaults')) {
variable_del('color_'. $theme .'_palette');
variable_del('color_'. $theme .'_stylesheet');
variable_del('color_'. $theme .'_logo');
variable_del('color_'. $theme .'_files');
variable_del('color_'. $theme .'_screenshot');
return;
}
// Prepare target locations for generated files
$id = $theme .'-'. substr(md5(serialize($palette) . microtime()), 0, 8);
$paths['color'] = variable_get('file_directory_path', 'files') .'/color';
$paths['target'] = $paths['color'] .'/'. $id .'/';
foreach ($paths as $path) {
if (!is_dir($path)) {
mkdir($path);
}
}
$paths['id'] = $id;
$paths['source'] = drupal_get_path('theme', $theme) .'/';
$paths['stylesheet'] = $paths['target'] .'style.css';
$paths['files'] = $paths['map'] = array();
// Save palette and stylesheet location
variable_set('color_'. $theme .'_palette', $palette);
variable_set('color_'. $theme .'_stylesheet', $paths['stylesheet']);
variable_set('color_'. $theme .'_logo', $paths['target'] .'logo.png');
// Copy over neutral images
foreach ($info['copy'] as $file) {
$base = basename($file);
copy($paths['source'] . $file, $paths['target'] . $base);
$paths['map'][$file] = $base;
$paths['files'][] = $paths['target'] . $base;
}
// Render new images
_color_render_images($theme, $info, $paths, $palette);
// Rewrite stylesheet
_color_rewrite_stylesheet($theme, $info, $paths, $palette);
// Maintain list of files
variable_set('color_'. $theme .'_files', $paths['files']);
}
/**
* Rewrite the stylesheet to match the colors in the palette.
*/
function _color_rewrite_stylesheet($theme, &$info, &$paths, $palette) {
// Load stylesheet
$style = file_get_contents($paths['source'] .'style.css');
// Prepare color conversion table
$conversion = $palette;
unset($conversion['base']);
foreach ($conversion as $k => $v) {
$conversion[$k] = drupal_strtolower($v);
}
$default = color_get_palette($theme, true);
// Split off the "Don't touch" section of the stylesheet.
list($style, $fixed) = explode("Color Module: Don't touch", $style);
// Look for @import commands and insert the referenced stylesheets.
$cwd = getcwd();
chdir(drupal_get_path('theme', $theme));
$style = preg_replace_callback('/@import\s*["\']([^"\']+)["\'];/', '_color_import_stylesheet', $style);
chdir($cwd);
// Find all colors in the stylesheet and the chunks in between.
$style = preg_split('/(#[0-9a-f]{6}|#[0-9a-f]{3})/i', $style, -1, PREG_SPLIT_DELIM_CAPTURE);
$is_color = false;
$output = '';
$base = 'base';
// Iterate over all parts
foreach ($style as $chunk) {
if ($is_color) {
$chunk = drupal_strtolower($chunk);
// Check if this is one of the colors in the default palette
if ($key = array_search($chunk, $default)) {
$chunk = $conversion[$key];
}
// Not a pre-set color. Extrapolate from the base.
else {
$chunk = _color_shift($palette[$base], $default[$base], $chunk, $info['blend_target']);
}
}
else {
// Determine the most suitable base color for the next color.
// 'a' declarations. Use link.
if (preg_match('@[^a-z0-9_-](a)[^a-z0-9_-][^/{]*{[^{]+$@i', $chunk)) {
$base = 'link';
}
// 'color:' styles. Use text.
else if (preg_match('/(? $after) {
$output = str_replace($before, $after, $output);
}
// Write new stylesheet
$file = fopen($paths['stylesheet'], 'w+');
fwrite($file, $output);
fclose($file);
$paths['files'][] = $paths['stylesheet'];
}
/**
* Helper function for _color_rewrite_stylesheet.
*/
function _color_import_stylesheet($matches) {
return preg_replace('/url\(([\'"]?)(?![a-z]+:)/i', 'url(\1'. dirname($matches[1]) .'/', file_get_contents($matches[1]));
}
/**
* Render images that match a given palette.
*/
function _color_render_images($theme, &$info, &$paths, $palette) {
// Prepare template image
$source = $paths['source'] .'/'. $info['base_image'];
$source = imagecreatefrompng($source);
// Prepare target buffer
$width = imagesx($source);
$height = imagesy($source);
$target = imagecreatetruecolor($width, $height);
imagealphablending($target, true);
// Fill regions of solid color.
foreach ($info['fill'] as $color => $fill) {
imagefilledrectangle($target, $fill[0], $fill[1], $fill[0] + $fill[2], $fill[1] + $fill[3], _color_gd($target, $palette[$color]));
}
// Render gradient
for ($y = 0; $y < $info['gradient'][3]; ++$y) {
$color = _color_blend($target, $palette['top'], $palette['bottom'], $y / ($info['gradient'][3] - 1));
imagefilledrectangle($target, $info['gradient'][0], $info['gradient'][1] + $y, $info['gradient'][2], $info['gradient'][1] + $y + 1, $color);
}
// Blend over template
imagecopy($target, $source, 0, 0, 0, 0, $width, $height);
// Cut out slices
foreach ($info['slices'] as $file => $coord) {
list($x, $y, $width, $height) = $coord;
$base = basename($file);
$image = $paths['target'] . $base;
// Cut out slice
if ($file == 'screenshot.png') {
$slice = imagecreatetruecolor(150, 90);
imagecopyresampled($slice, $target, 0, 0, $x, $y, 150, 90, $width, $height);
variable_set('color_'. $theme .'_screenshot', $image);
}
else {
$slice = imagecreatetruecolor($width, $height);
imagecopy($slice, $target, 0, 0, $x, $y, $width, $height);
}
// Save image
imagepng($slice, $image);
imagedestroy($slice);
$paths['files'][] = $image;
// Build before/after map of image paths.
$paths['map'][$file] = $base;
}
// Clean up
imagedestroy($source);
imagedestroy($target);
}
/**
* Shift a given color, using a reference pair and a target blend color.
*
* Note: this function is significantly different from the JS version, as it
* is written to match the blended images perfectly.
*
* Constraint: if (ref2 == target + (ref1 - target) * delta) for some fraction delta
* then (return == target + (given - target) * delta)
*
* Loose constraint: Preserve relative positions in saturation and luminance
* space.
*/
function _color_shift($given, $ref1, $ref2, $target) {
// We assume that ref2 is a blend of ref1 and target and find
// delta based on the length of the difference vectors:
// delta = 1 - |ref2 - ref1| / |white - ref1|
$target = _color_unpack($target, true);
$ref1 = _color_unpack($ref1, true);
$ref2 = _color_unpack($ref2, true);
for ($i = 0; $i < 3; ++$i) {
$numerator += ($ref2[$i] - $ref1[$i]) * ($ref2[$i] - $ref1[$i]);
$denominator += ($target[$i] - $ref1[$i]) * ($target[$i] - $ref1[$i]);
}
$delta = ($denominator > 0) ? (1 - sqrt($numerator / $denominator)) : 0;
// Calculate the color that ref2 would be if the assumption was true.
for ($i = 0; $i < 3; ++$i) {
$ref3[$i] = $target[$i] + ($ref1[$i] - $target[$i]) * $delta;
}
// If the assumption is not true, there is a difference between ref2 and ref3.
// We measure this in HSL space. Notation: x' = hsl(x).
$ref2 = _color_rgb2hsl($ref2);
$ref3 = _color_rgb2hsl($ref3);
for ($i = 0; $i < 3; ++$i) {
$shift[$i] = $ref2[$i] - $ref3[$i];
}
// Take the given color, and blend it towards the target.
$given = _color_unpack($given, true);
for ($i = 0; $i < 3; ++$i) {
$result[$i] = $target[$i] + ($given[$i] - $target[$i]) * $delta;
}
// Finally, we apply the extra shift in HSL space.
// Note: if ref2 is a pure blend of ref1 and target, then |shift| = 0.
$result = _color_rgb2hsl($result);
for ($i = 0; $i < 3; ++$i) {
$result[$i] = min(1, max(0, $result[$i] + $shift[$i]));
}
$result = _color_hsl2rgb($result);
// Return hex color.
return _color_pack($result, true);
}
/**
* Convert a hex triplet into a GD color.
*/
function _color_gd($img, $hex) {
$c = array_merge(array($img), _color_unpack($hex));
return call_user_func_array('imagecolorallocate', $c);
}
/**
* Blend two hex colors and return the GD color.
*/
function _color_blend($img, $hex1, $hex2, $alpha) {
$in1 = _color_unpack($hex1);
$in2 = _color_unpack($hex2);
$out = array($img);
for ($i = 0; $i < 3; ++$i) {
$out[] = $in1[$i] + ($in2[$i] - $in1[$i]) * $alpha;
}
return call_user_func_array('imagecolorallocate', $out);
}
/**
* Convert a hex color into an RGB triplet.
*/
function _color_unpack($hex, $normalize = false) {
if (strlen($hex) == 4) {
$hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3];
}
$c = hexdec($hex);
for ($i = 16; $i >= 0; $i -= 8) {
$out[] = (($c >> $i) & 0xFF) / ($normalize ? 255 : 1);
}
return $out;
}
/**
* Convert an RGB triplet to a hex color.
*/
function _color_pack($rgb, $normalize = false) {
foreach ($rgb as $k => $v) {
$out |= (($v * ($normalize ? 255 : 1)) << (16 - $k * 8));
}
return '#'. str_pad(dechex($out), 6, 0, STR_PAD_LEFT);
}
/**
* Convert a HSL triplet into RGB
*/
function _color_hsl2rgb($hsl) {
$h = $hsl[0];
$s = $hsl[1];
$l = $hsl[2];
$m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l*$s;
$m1 = $l * 2 - $m2;
return array(_color_hue2rgb($m1, $m2, $h + 0.33333),
_color_hue2rgb($m1, $m2, $h),
_color_hue2rgb($m1, $m2, $h - 0.33333));
}
/**
* Helper function for _color_hsl2rgb().
*/
function _color_hue2rgb($m1, $m2, $h) {
$h = ($h < 0) ? $h + 1 : (($h > 1) ? $h - 1 : $h);
if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6;
if ($h * 2 < 1) return $m2;
if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (0.66666 - $h) * 6;
return $m1;
}
/**
* Convert an RGB triplet to HSL.
*/
function _color_rgb2hsl($rgb) {
$r = $rgb[0];
$g = $rgb[1];
$b = $rgb[2];
$min = min($r, min($g, $b));
$max = max($r, max($g, $b));
$delta = $max - $min;
$l = ($min + $max) / 2;
$s = 0;
if ($l > 0 && $l < 1) {
$s = $delta / ($l < 0.5 ? (2 * $l) : (2 - 2 * $l));
}
$h = 0;
if ($delta > 0) {
if ($max == $r && $max != $g) $h += ($g - $b) / $delta;
if ($max == $g && $max != $b) $h += (2 + ($b - $r) / $delta);
if ($max == $b && $max != $r) $h += (4 + ($r - $g) / $delta);
$h /= 6;
}
return array($h, $s, $l);
}