Code coverage for /20080809/modules/color/color.module

Line #Times calledCode
1
<?php
2
// $Id: color.module,v 1.42 2008/07/16 21:59:26 dries Exp $
3
4
/**
5
 * Implementation of hook_help().
6
 */
72027
function color_help($path, $arg) {
8
  switch ($path) {
91491
    case 'admin/help#color':
1011
      $output = '<p>' . t('The color module allows a site administrator to
quickly and easily change the color scheme of certain themes. Although not
all themes support color module, both Garland (the default theme) and
Minnelli were designed to take advantage of its features. By using color
module with a compatible theme, you can easily change the color of links,
backgrounds, text, and other theme elements. Color module requires that
your <a href="@url">file download method</a> be set to public.',
array('@url' => url('admin/settings/file-system'))) . '</p>';
1111
      $output .= '<p>' . t("It is important to remember that color module
saves a modified copy of the theme's specified stylesheets in the files
directory. This means that if you make any manual changes to your theme's
stylesheet, you must save your color settings again, even if they haven't
changed. This causes the color module generated version of the stylesheets
in the files directory to be recreated using the new version of the
original file.") . '</p>';
1211
      $output .= '<p>' . t('To change the color settings for a compatible
theme, select the "configure" link for the theme on the <a
href="@themes">themes administration page</a>.', array('@themes' =>
url('admin/build/themes'))) . '</p>';
1311
      $output .= '<p>' . t('For more information, see the online handbook
entry for <a href="@color">Color module</a>.', array('@color' =>
'http://drupal.org/handbook/modules/color/')) . '</p>';
14
1511
      return $output;
160
  }
171489
}
18
19
/**
20
 * Implementation of hook_theme().
21
 */
222027
function color_theme() {
23
  return array(
24
    'color_scheme_form' => array(
2591
      'arguments' => array('form' => NULL),
2691
    ),
2791
  );
280
}
29
30
/**
31
 * Implementation of hook_form_alter().
32
 */
332027
function color_form_alter(&$form, $form_state, $form_id) {
34
  // Insert the color changer into the theme settings page.
351338
  if ($form_id == 'system_theme_settings' && color_get_info(arg(4)) &&
function_exists('gd_info')) {
360
    if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) !=
FILE_DOWNLOADS_PUBLIC) {
37
      // Disables the color changer when the private download method is
used.
38
      // TODO: This should be solved in a different way. See issue
#181003.
390
      drupal_set_message(t('The color picker only works if the <a
href="@url">download method</a> is set to public.', array('@url' =>
url('admin/settings/file-system'))), 'warning');
400
    }
41
    else {
420
      $form['color'] = array(
430
        '#type' => 'fieldset',
440
        '#title' => t('Color scheme'),
450
        '#weight' => -1,
460
        '#attributes' => array('id' => 'color_scheme_form'),
470
        '#theme' => 'color_scheme_form',
48
      );
490
      $form['color'] += color_scheme_form($form_state, arg(4));
500
      $form['#submit'][] = 'color_scheme_form_submit';
51
    }
520
  }
53
54
  // Use the generated screenshot in the theme list.
551338
  if ($form_id == 'system_theme_select_form' || $form_id ==
'system_themes') {
560
    $themes = list_themes();
570
    foreach (element_children($form) as $theme) {
580
      if ($screenshot = variable_get('color_' . $theme . '_screenshot',
NULL)) {
590
        if (isset($form[$theme]['screenshot'])) {
600
          $form[$theme]['screenshot']['#markup'] = theme('image',
$screenshot, '', '', array('class' => 'screenshot'), FALSE);
610
        }
620
      }
630
    }
640
  }
651338
}
66
67
/**
68
 * Callback for the theme to alter the resources used.
69
 */
702027
function _color_page_alter(&$vars) {
711489
  global $language, $theme_key;
72
73
  // Override stylesheets.
741489
  $color_paths = variable_get('color_' . $theme_key . '_stylesheets',
array());
751489
  if (!empty($color_paths)) {
76
    // Loop over theme CSS files and try to rebuild CSS array with
rewritten
77
    // stylesheets. Keep the orginal order intact for CSS cascading.
780
    $new_theme_css = array();
79
800
    foreach ($vars['css']['all']['theme'] as $old_path => $old_preprocess)
{
81
      // Add the non-colored stylesheet first as we might not find a
82
      // re-colored stylesheet for replacement later.
830
      $new_theme_css[$old_path] = $old_preprocess;
84
85
      // Loop over the path array with recolored CSS files to find
matching
86
      // paths which could replace the non-recolored paths.
870
      foreach ($color_paths as $color_path) {
88
        // Color module currently requires unique file names to be used,
89
        // which allows us to compare different file paths.
900
        if (basename($old_path) == basename($color_path)) {
91
          // Pull out the non-colored and add rewritten stylesheet.
920
          unset($new_theme_css[$old_path]);
930
          $new_theme_css[$color_path] = $old_preprocess;
94
95
          // If the current language is RTL and the CSS file had an RTL
variant,
96
          // pull out the non-colored and add rewritten RTL stylesheet.
970
          if (defined('LANGUAGE_RTL') && $language->direction ==
LANGUAGE_RTL) {
980
            $rtl_old_path = str_replace('.css', '-rtl.css', $old_path);
990
            $rtl_color_path = str_replace('.css', '-rtl.css',
$color_path);
1000
            if (file_exists($rtl_color_path)) {
1010
              unset($new_theme_css[$rtl_old_path]);
1020
              $new_theme_css[$rtl_color_path] = $old_preprocess;
1030
            }
1040
          }
1050
          break;
1060
        }
1070
      }
1080
    }
1090
    $vars['css']['all']['theme'] = $new_theme_css;
1100
    $vars['styles'] = drupal_get_css($vars['css']);
1110
  }
112
113
  // Override logo.
1141489
  $logo = variable_get('color_' . $theme_key . '_logo', NULL);
1151489
  if ($logo && $vars['logo'] && preg_match('!' . $theme_key .
'/logo.png$!', $vars['logo'])) {
1160
    $vars['logo'] = base_path() . $logo;
1170
  }
1181489
}
119
120
/**
121
 * Retrieve the color.module info for a particular theme.
122
 */
1232027
function color_get_info($theme) {
1240
  $path = drupal_get_path('theme', $theme);
1250
  $file = $path . '/color/color.inc';
1260
  if ($path && file_exists($file)) {
1270
    include $file;
1280
    return $info;
1290
  }
1300
}
131
132
/**
133
 * Helper function to retrieve the color palette for a particular theme.
134
 */
1352027
function color_get_palette($theme, $default = false) {
136
  // Fetch and expand default palette.
1370
  $fields = array('base', 'link', 'top', 'bottom', 'text');
1380
  $info = color_get_info($theme);
1390
  $keys = array_keys($info['schemes']);
1400
  foreach (explode(',', array_shift($keys)) as $k => $scheme) {
1410
    $palette[$fields[$k]] = $scheme;
1420
  }
143
144
  // Load variable.
1450
  return $default ? $palette : variable_get('color_' . $theme . '_palette',
$palette);
1460
}
147
148
/**
149
 * Form callback. Returns the configuration form.
150
 */
1512027
function color_scheme_form(&$form_state, $theme) {
1520
  $base = drupal_get_path('module', 'color');
1530
  $info = color_get_info($theme);
154
155
  // Add Farbtastic color picker.
1560
  drupal_add_css('misc/farbtastic/farbtastic.css', 'module', 'all',
FALSE);
1570
  drupal_add_js('misc/farbtastic/farbtastic.js');
158
159
  // Add custom CSS and JS.
1600
  drupal_add_css($base . '/color.css', 'module', 'all', FALSE);
1610
  drupal_add_js($base . '/color.js');
1620
  drupal_add_js(array('color' => array(
1630
    'reference' => color_get_palette($theme, true)
1640
  )), 'setting');
165
166
  // See if we're using a predefined scheme.
1670
  $current = implode(',', variable_get('color_' . $theme . '_palette',
array()));
168
  // Note: we use the original theme when the default scheme is chosen.
1690
  $current = isset($info['schemes'][$current]) ? $current : ($current == ''
? reset($info['schemes']) : '');
170
171
  // Add scheme selector.
1720
  $info['schemes'][''] = t('Custom');
1730
  $form['scheme'] = array(
1740
    '#type' => 'select',
1750
    '#title' => t('Color set'),
1760
    '#options' => $info['schemes'],
1770
    '#default_value' => $current,
178
  );
179
180
  // Add palette fields.
1810
  $palette = color_get_palette($theme);
182
  $names = array(
1830
    'base' => t('Base color'),
1840
    'link' => t('Link color'),
1850
    'top' => t('Header top'),
1860
    'bottom' => t('Header bottom'),
1870
    'text' => t('Text color'),
1880
  );
1890
  $form['palette']['#tree'] = true;
1900
  foreach ($palette as $name => $value) {
1910
    $form['palette'][$name] = array(
1920
      '#type' => 'textfield',
1930
      '#title' => $names[$name],
1940
      '#default_value' => $value,
1950
      '#size' => 8,
196
    );
1970
  }
1980
  $form['theme'] = array('#type' => 'value', '#value' => arg(4));
1990
  $form['info'] = array('#type' => 'value', '#value' => $info);
200
2010
  return $form;
2020
}
203
204
/**
205
 * Theme the color form.
206
 *
207
 * @ingroup @themeable
208
 */
2092027
function theme_color_scheme_form($form) {
2100
  $theme = $form['theme']['#value'];
2110
  $info = $form['info']['#value'];
2120
  $path = drupal_get_path('theme', $theme) . '/';
2130
  drupal_add_css($path . $info['preview_css']);
214
2150
  $output  = '';
2160
  $output .= '<div class="color-form clear-block">';
217
  // Color schemes
2180
  $output .= drupal_render($form['scheme']);
219
  // Palette
2200
  $output .= '<div id="palette" class="clear-block">';
2210
  foreach (element_children($form['palette']) as $name) {
2220
    $output .= drupal_render($form['palette'][$name]);
2230
  }
2240
  $output .= '</div>';
225
  // Preview
2260
  $output .= drupal_render($form);
2270
  $output .= '<h2>' . t('Preview') . '</h2>';
2280
  $output .= '<div id="preview"><div id="text"><h2>Lorem ipsum
dolor</h2><p>Sit amet, consectetur adipisicing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud <a href="#">exercitation ullamco</a> laboris nisi ut aliquip ex ea
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.</p></div><div id="img" style="background-image: url(' .
base_path() . $path . $info['preview_image'] . ')"></div></div>';
229
  // Close the wrapper div.
2300
  $output .= '</div>';
231
2320
  return $output;
2330
}
234
235
/**
236
 * Submit handler for color change form.
237
 */
2382027
function color_scheme_form_submit($form, &$form_state) {
239
  // Get theme coloring info.
2400
  if (!isset($form_state['values']['info'])) {
2410
    return;
2420
  }
2430
  $theme = $form_state['values']['theme'];
2440
  $info = $form_state['values']['info'];
245
246
  // Resolve palette.
2470
  $palette = $form_state['values']['palette'];
2480
  if ($form_state['values']['scheme'] != '') {
2490
    $scheme = explode(',', $form_state['values']['scheme']);
2500
    foreach ($palette as $k => $color) {
2510
      $palette[$k] = array_shift($scheme);
2520
    }
2530
  }
254
255
  // Make sure enough memory is available, if PHP's memory limit is
compiled in.
2560
  if (function_exists('memory_get_usage')) {
257
    // Fetch source image dimensions.
2580
    $source = drupal_get_path('theme', $theme) . '/' .
$info['base_image'];
2590
    list($width, $height) = getimagesize($source);
260
261
    // We need at least a copy of the source and a target buffer of the
same
262
    // size (both at 32bpp).
2630
    $required = $width * $height * 8;
2640
    $usage = memory_get_usage();
2650
    $limit = parse_size(ini_get('memory_limit'));
2660
    if ($usage + $required > $limit) {
2670
      drupal_set_message(t('There is not enough memory available to PHP to
change this theme\'s color scheme. You need at least %size more. Check the
<a href="@url">PHP documentation</a> for more information.', array('%size'
=> format_size($usage + $required - $limit), '@url' =>
'http://www.php.net/manual/en/ini.core.php#ini.sect.resource-limits')),
'error');
2680
      return;
2690
    }
2700
  }
271
272
  // Delete old files.
2730
  foreach (variable_get('color_' . $theme . '_files', array()) as $file) {
2740
    @unlink($file);
2750
  }
2760
  if (isset($file) && $file = dirname($file)) {
2770
    @rmdir($file);
2780
  }
279
280
  // Don't render the default colorscheme, use the standard theme instead.
2810
  if (implode(',', color_get_palette($theme, true)) == implode(',',
$palette)
2820
    || $form_state['values']['op'] == t('Reset to defaults')) {
2830
    variable_del('color_' . $theme . '_palette');
2840
    variable_del('color_' . $theme . '_stylesheets');
2850
    variable_del('color_' . $theme . '_logo');
2860
    variable_del('color_' . $theme . '_files');
2870
    variable_del('color_' . $theme . '_screenshot');
2880
    return;
2890
  }
290
291
  // Prepare target locations for generated files.
2920
  $id = $theme . '-' . substr(md5(serialize($palette) . microtime()), 0,
8);
2930
  $paths['color'] = file_directory_path() . '/color';
2940
  $paths['target'] = $paths['color'] . '/' . $id;
2950
  foreach ($paths as $path) {
2960
    file_check_directory($path, FILE_CREATE_DIRECTORY);
2970
  }
2980
  $paths['target'] = $paths['target'] . '/';
2990
  $paths['id'] = $id;
3000
  $paths['source'] = drupal_get_path('theme', $theme) . '/';
3010
  $paths['files'] = $paths['map'] = array();
302
303
  // Save palette and logo location.
3040
  variable_set('color_' . $theme . '_palette', $palette);
3050
  variable_set('color_' . $theme . '_logo', $paths['target'] .
'logo.png');
306
307
  // Copy over neutral images.
3080
  foreach ($info['copy'] as $file) {
3090
    $base = basename($file);
3100
    $source = $paths['source'] . $file;
3110
    file_copy($source, $paths['target'] . $base);
3120
    $paths['map'][$file] = $base;
3130
    $paths['files'][] = $paths['target'] . $base;
3140
  }
315
316
  // Render new images, if image has been provided.
3170
  if ($info['base_image']) {
3180
    _color_render_images($theme, $info, $paths, $palette);
3190
  }
320
321
  // Rewrite theme stylesheets.
3220
  $css = array();
3230
  foreach ($info['css'] as $stylesheet) {
324
    // Build a temporary array with LTR and RTL files.
3250
    $files = array();
3260
    if (file_exists($paths['source'] . $stylesheet)) {
3270
      $files[] = $stylesheet;
328
3290
      $rtl_file = str_replace('.css', '-rtl.css', $stylesheet);
3300
      if (file_exists($paths['source'] . $rtl_file)) {
3310
        $files[] = $rtl_file;
3320
      }
3330
    }
334
3350
    foreach ($files as $file) {
336
      // Aggregate @imports recursively for each configured top level CSS
file
337
      // without optimization. Aggregation and optimization will be
338
      // handled by drupal_build_css_cache() only.
3390
      $style = drupal_load_stylesheet($paths['source'] . $file, FALSE);
340
341
      // Return the path to where this CSS file originated from, stripping
342
      // off the name of the file at the end of the path.
3430
      $base = base_path() . dirname($paths['source'] . $file) . '/';
3440
      _drupal_build_css_path(NULL, $base);
345
346
      // Prefix all paths within this CSS file, ignoring absolute paths.
3470
      $style =
preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i',
'_drupal_build_css_path', $style);
348
349
      // Rewrite stylesheet with new colors.
3500
      $style = _color_rewrite_stylesheet($theme, $info, $paths, $palette,
$style);
3510
      $base_file = basename($file);
3520
      $css[] = $paths['target'] . $base_file;
3530
      _color_save_stylesheet($paths['target'] . $base_file, $style,
$paths);
3540
    }
3550
  }
356
357
  // Maintain list of files.
3580
  variable_set('color_' . $theme . '_stylesheets', $css);
3590
  variable_set('color_' . $theme . '_files', $paths['files']);
3600
}
361
362
/**
363
 * Rewrite the stylesheet to match the colors in the palette.
364
 */
3652027
function _color_rewrite_stylesheet($theme, &$info, &$paths, $palette,
$style) {
3660
  $themes = list_themes();
367
  // Prepare color conversion table.
3680
  $conversion = $palette;
3690
  unset($conversion['base']);
3700
  foreach ($conversion as $k => $v) {
3710
    $conversion[$k] = drupal_strtolower($v);
3720
  }
3730
  $default = color_get_palette($theme, true);
374
375
  // Split off the "Don't touch" section of the stylesheet.
3760
  $split = "Color Module: Don't touch";
3770
  if (strpos($style, $split) !== FALSE) {
3780
    list($style, $fixed) = explode($split, $style);
3790
  }
380
381
  // Find all colors in the stylesheet and the chunks in between.
3820
  $style = preg_split('/(#[0-9a-f]{6}|#[0-9a-f]{3})/i', $style, -1,
PREG_SPLIT_DELIM_CAPTURE);
3830
  $is_color = false;
3840
  $output = '';
3850
  $base = 'base';
386
387
  // Iterate over all the parts.
3880
  foreach ($style as $chunk) {
3890
    if ($is_color) {
3900
      $chunk = drupal_strtolower($chunk);
391
      // Check if this is one of the colors in the default palette.
3920
      if ($key = array_search($chunk, $default)) {
3930
        $chunk = $conversion[$key];
3940
      }
395
      // Not a pre-set color. Extrapolate from the base.
396
      else {
3970
        $chunk = _color_shift($palette[$base], $default[$base], $chunk,
$info['blend_target']);
398
      }
3990
    }
400
    else {
401
      // Determine the most suitable base color for the next color.
402
403
      // 'a' declarations. Use link.
4040
      if (preg_match('@[^a-z0-9_-](a)[^a-z0-9_-][^/{]*{[^{]+$@i', $chunk))
{
4050
        $base = 'link';
4060
      }
407
      // 'color:' styles. Use text.
4080
      elseif (preg_match('/(?<!-)color[^{:]*:[^{#]*$/i', $chunk)) {
4090
        $base = 'text';
4100
      }
411
      // Reset back to base.
412
      else {
4130
        $base = 'base';
414
      }
415
    }
4160
    $output .= $chunk;
4170
    $is_color = !$is_color;
4180
  }
419
  // Append fixed colors segment.
4200
  if (isset($fixed)) {
4210
    $output .= $fixed;
4220
  }
423
424
  // Replace paths to images.
4250
  foreach ($paths['map'] as $before => $after) {
4260
    $before = base_path() . $paths['source'] . $before;
4270
    $before = preg_replace('`(^|/)(?!../)([^/]+)/../`', '$1', $before);
4280
    $output = str_replace($before, $after, $output);
4290
  }
430
4310
  return $output;
4320
}
433
434
/**
435
 * Save the rewritten stylesheet to disk.
436
 */
4372027
function _color_save_stylesheet($file, $style, &$paths) {
4380
  file_save_data($style, $file, FILE_EXISTS_REPLACE);
4390
  $paths['files'][] = $file;
440
441
  // Set standard file permissions for webserver-generated files.
4420
  @chmod($file, 0664);
4430
}
444
445
/**
446
 * Render images that match a given palette.
447
 */
4482027
function _color_render_images($theme, &$info, &$paths, $palette) {
449
450
  // Prepare template image.
4510
  $source = $paths['source'] . '/' . $info['base_image'];
4520
  $source = imagecreatefrompng($source);
4530
  $width = imagesx($source);
4540
  $height = imagesy($source);
455
456
  // Prepare target buffer.
4570
  $target = imagecreatetruecolor($width, $height);
4580
  imagealphablending($target, true);
459
460
  // Fill regions of solid color.
4610
  foreach ($info['fill'] as $color => $fill) {
4620
    imagefilledrectangle($target, $fill[0], $fill[1], $fill[0] + $fill[2],
$fill[1] + $fill[3], _color_gd($target, $palette[$color]));
4630
  }
464
465
  // Render gradient.
4660
  for ($y = 0; $y < $info['gradient'][3]; ++$y) {
4670
    $color = _color_blend($target, $palette['top'], $palette['bottom'], $y
/ ($info['gradient'][3] - 1));
4680
    imagefilledrectangle($target, $info['gradient'][0],
$info['gradient'][1] + $y, $info['gradient'][0] + $info['gradient'][2],
$info['gradient'][1] + $y + 1, $color);
4690
  }
470
471
  // Blend over template.
4720
  imagecopy($target, $source, 0, 0, 0, 0, $width, $height);
473
474
  // Clean up template image.
4750
  imagedestroy($source);
476
477
  // Cut out slices.
4780
  foreach ($info['slices'] as $file => $coord) {
4790
    list($x, $y, $width, $height) = $coord;
4800
    $base = basename($file);
4810
    $image = $paths['target'] . $base;
482
483
    // Cut out slice.
4840
    if ($file == 'screenshot.png') {
4850
      $slice = imagecreatetruecolor(150, 90);
4860
      imagecopyresampled($slice, $target, 0, 0, $x, $y, 150, 90, $width,
$height);
4870
      variable_set('color_' . $theme . '_screenshot', $image);
4880
    }
489
    else {
4900
      $slice = imagecreatetruecolor($width, $height);
4910
      imagecopy($slice, $target, 0, 0, $x, $y, $width, $height);
492
    }
493
494
    // Save image.
4950
    imagepng($slice, $image);
4960
    imagedestroy($slice);
4970
    $paths['files'][] = $image;
498
499
    // Set standard file permissions for webserver-generated files
5000
    @chmod(realpath($image), 0664);
501
502
    // Build before/after map of image paths.
5030
    $paths['map'][$file] = $base;
5040
  }
505
506
  // Clean up target buffer.
5070
  imagedestroy($target);
5080
}
509
510
/**
511
 * Shift a given color, using a reference pair and a target blend color.
512
 *
513
 * Note: this function is significantly different from the JS version, as
it
514
 * is written to match the blended images perfectly.
515
 *
516
 * Constraint: if (ref2 == target + (ref1 - target) * delta) for some
fraction delta
517
 *              then (return == target + (given - target) * delta)
518
 *
519
 * Loose constraint: Preserve relative positions in saturation and
luminance
520
 *                   space.
521
 */
5222027
function _color_shift($given, $ref1, $ref2, $target) {
523
  // We assume that ref2 is a blend of ref1 and target and find
524
  // delta based on the length of the difference vectors.
525
526
  // delta = 1 - |ref2 - ref1| / |white - ref1|
5270
  $target = _color_unpack($target, true);
5280
  $ref1 = _color_unpack($ref1, true);
5290
  $ref2 = _color_unpack($ref2, true);
5300
  $numerator = 0;
5310
  $denominator = 0;
5320
  for ($i = 0; $i < 3; ++$i) {
5330
    $numerator += ($ref2[$i] - $ref1[$i]) * ($ref2[$i] - $ref1[$i]);
5340
    $denominator += ($target[$i] - $ref1[$i]) * ($target[$i] - $ref1[$i]);
5350
  }
5360
  $delta = ($denominator > 0) ? (1 - sqrt($numerator / $denominator)) : 0;
537
538
  // Calculate the color that ref2 would be if the assumption was true.
5390
  for ($i = 0; $i < 3; ++$i) {
5400
    $ref3[$i] = $target[$i] + ($ref1[$i] - $target[$i]) * $delta;
5410
  }
542
543
  // If the assumption is not true, there is a difference between ref2 and
ref3.
544
  // We measure this in HSL space. Notation: x' = hsl(x).
5450
  $ref2 = _color_rgb2hsl($ref2);
5460
  $ref3 = _color_rgb2hsl($ref3);
5470
  for ($i = 0; $i < 3; ++$i) {
5480
    $shift[$i] = $ref2[$i] - $ref3[$i];
5490
  }
550
551
  // Take the given color, and blend it towards the target.
5520
  $given = _color_unpack($given, true);
5530
  for ($i = 0; $i < 3; ++$i) {
5540
    $result[$i] = $target[$i] + ($given[$i] - $target[$i]) * $delta;
5550
  }
556
557
  // Finally, we apply the extra shift in HSL space.
558
  // Note: if ref2 is a pure blend of ref1 and target, then |shift| = 0.
5590
  $result = _color_rgb2hsl($result);
5600
  for ($i = 0; $i < 3; ++$i) {
5610
    $result[$i] = min(1, max(0, $result[$i] + $shift[$i]));
5620
  }
5630
  $result = _color_hsl2rgb($result);
564
565
  // Return hex color.
5660
  return _color_pack($result, true);
5670
}
568
569
/**
570
 * Convert a hex triplet into a GD color.
571
 */
5722027
function _color_gd($img, $hex) {
5730
  $c = array_merge(array($img), _color_unpack($hex));
5740
  return call_user_func_array('imagecolorallocate', $c);
5750
}
576
577
/**
578
 * Blend two hex colors and return the GD color.
579
 */
5802027
function _color_blend($img, $hex1, $hex2, $alpha) {
5810
  $in1 = _color_unpack($hex1);
5820
  $in2 = _color_unpack($hex2);
5830
  $out = array($img);
5840
  for ($i = 0; $i < 3; ++$i) {
5850
    $out[] = $in1[$i] + ($in2[$i] - $in1[$i]) * $alpha;
5860
  }
587
5880
  return call_user_func_array('imagecolorallocate', $out);
5890
}
590
591
/**
592
 * Convert a hex color into an RGB triplet.
593
 */
5942027
function _color_unpack($hex, $normalize = false) {
5950
  if (strlen($hex) == 4) {
5960
    $hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3];
5970
  }
5980
  $c = hexdec($hex);
5990
  for ($i = 16; $i >= 0; $i -= 8) {
6000
    $out[] = (($c >> $i) & 0xFF) / ($normalize ? 255 : 1);
6010
  }
602
6030
  return $out;
6040
}
605
606
/**
607
 * Convert an RGB triplet to a hex color.
608
 */
6092027
function _color_pack($rgb, $normalize = false) {
6100
  $out = 0;
6110
  foreach ($rgb as $k => $v) {
6120
    $out |= (($v * ($normalize ? 255 : 1)) << (16 - $k * 8));
6130
  }
614
6150
  return '#' . str_pad(dechex($out), 6, 0, STR_PAD_LEFT);
6160
}
617
618
/**
619
 * Convert a HSL triplet into RGB.
620
 */
6212027
function _color_hsl2rgb($hsl) {
6220
  $h = $hsl[0];
6230
  $s = $hsl[1];
6240
  $l = $hsl[2];
6250
  $m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l*$s;
6260
  $m1 = $l * 2 - $m2;
627
628
  return array(
6290
    _color_hue2rgb($m1, $m2, $h + 0.33333),
6300
    _color_hue2rgb($m1, $m2, $h),
6310
    _color_hue2rgb($m1, $m2, $h - 0.33333),
6320
  );
6330
}
634
635
/**
636
 * Helper function for _color_hsl2rgb().
637
 */
6382027
function _color_hue2rgb($m1, $m2, $h) {
6390
  $h = ($h < 0) ? $h + 1 : (($h > 1) ? $h - 1 : $h);
6400
  if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6;
6410
  if ($h * 2 < 1) return $m2;
6420
  if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (0.66666 - $h) * 6;
643
6440
  return $m1;
6450
}
646
647
/**
648
 * Convert an RGB triplet to HSL.
649
 */
6502027
function _color_rgb2hsl($rgb) {
6510
  $r = $rgb[0];
6520
  $g = $rgb[1];
6530
  $b = $rgb[2];
6540
  $min = min($r, min($g, $b));
6550
  $max = max($r, max($g, $b));
6560
  $delta = $max - $min;
6570
  $l = ($min + $max) / 2;
6580
  $s = 0;
659
6600
  if ($l > 0 && $l < 1) {
6610
    $s = $delta / ($l < 0.5 ? (2 * $l) : (2 - 2 * $l));
6620
  }
663
6640
  $h = 0;
6650
  if ($delta > 0) {
6660
    if ($max == $r && $max != $g) $h += ($g - $b) / $delta;
6670
    if ($max == $g && $max != $b) $h += (2 + ($b - $r) / $delta);
6680
    if ($max == $b && $max != $r) $h += (4 + ($r - $g) / $delta);
6690
    $h /= 6;
6700
  }
671
6720
  return array($h, $s, $l);
6730
}
6742027