Simpletest Coverage - modules/color/color.module

1 <?php
2 // $Id: color.module,v 1.64 2009/08/05 19:40:55 webchick Exp $
3
4 /**
5 * Implement hook_help().
6 */
7 function color_help($path, $arg) {
8 switch ($path) {
9 case 'admin/help#color':
10 $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>';
11 $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>';
12 $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/appearance'))) . '</p>';
13 $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
15 return $output;
16 }
17 }
18
19 /**
20 * Implement hook_theme().
21 */
22 function color_theme() {
23 return array(
24 'color_scheme_form' => array(
25 'arguments' => array('form' => NULL),
26 ),
27 );
28 }
29
30 /**
31 * Implement hook_form_FORM_ID_alter().
32 */
33 function color_form_system_theme_settings_alter(&$form, &$form_state) {
34 if (color_get_info(arg(4)) && function_exists('gd_info')) {
35 if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) != FILE_DOWNLOADS_PUBLIC) {
36 // Disables the color changer when the private download method is used.
37 // TODO: This should be solved in a different way. See issue #181003.
38 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');
39 }
40 else {
41 $form['color'] = array(
42 '#type' => 'fieldset',
43 '#title' => t('Color scheme'),
44 '#weight' => -1,
45 '#attributes' => array('id' => 'color_scheme_form'),
46 '#theme' => 'color_scheme_form',
47 );
48 $form['color'] += color_scheme_form($form_state, arg(4));
49 $form['#submit'][] = 'color_scheme_form_submit';
50 }
51 }
52 }
53
54 /**
55 * Implement hook_form_FORM_ID_alter().
56 */
57 function color_form_system_themes_alter(&$form, &$form_state) {
58 _color_theme_select_form_alter($form, $form_state);
59 }
60
61 /**
62 * Helper for hook_form_FORM_ID_alter() implementations.
63 */
64 function _color_theme_select_form_alter(&$form, &$form_state) {
65 // Use the generated screenshot in the theme list.
66 $themes = list_themes();
67 foreach (element_children($form) as $theme) {
68 if ($screenshot = variable_get('color_' . $theme . '_screenshot')) {
69 if (isset($form[$theme]['screenshot'])) {
70 $form[$theme]['screenshot']['#markup'] = theme('image', $screenshot, '', '', array('class' => 'screenshot'), FALSE);
71 }
72 }
73 }
74 }
75
76 /**
77 * Callback for the theme to alter the resources used.
78 */
79 function _color_page_alter(&$vars) {
80 global $language, $theme_key;
81
82 // Override stylesheets.
83 $color_paths = variable_get('color_' . $theme_key . '_stylesheets', array());
84 if (!empty($color_paths)) {
85 // Loop over theme CSS files and try to rebuild CSS array with rewritten
86 // stylesheets. Keep the original order intact for CSS cascading.
87 $new_theme_css = array();
88
89 foreach ($vars['css']['all']['theme'] as $old_path => $old_preprocess) {
90 // Add the non-colored stylesheet first as we might not find a
91 // re-colored stylesheet for replacement later.
92 $new_theme_css[$old_path] = $old_preprocess;
93
94 // Loop over the path array with recolored CSS files to find matching
95 // paths which could replace the non-recolored paths.
96 foreach ($color_paths as $color_path) {
97 // Color module currently requires unique file names to be used,
98 // which allows us to compare different file paths.
99 if (basename($old_path) == basename($color_path)) {
100 // Pull out the non-colored and add rewritten stylesheet.
101 unset($new_theme_css[$old_path]);
102 $new_theme_css[$color_path] = $old_preprocess;
103
104 // If the current language is RTL and the CSS file had an RTL variant,
105 // pull out the non-colored and add rewritten RTL stylesheet.
106 if ($language->direction == LANGUAGE_RTL) {
107 $rtl_old_path = str_replace('.css', '-rtl.css', $old_path);
108 $rtl_color_path = str_replace('.css', '-rtl.css', $color_path);
109 if (file_exists($rtl_color_path)) {
110 unset($new_theme_css[$rtl_old_path]);
111 $new_theme_css[$rtl_color_path] = $old_preprocess;
112 }
113 }
114 break;
115 }
116 }
117 }
118 $vars['css']['all']['theme'] = $new_theme_css;
119 $vars['styles'] = drupal_get_css($vars['css']);
120 }
121
122 // Override logo.
123 $logo = variable_get('color_' . $theme_key . '_logo');
124 if ($logo && $vars['logo'] && preg_match('!' . $theme_key . '/logo.png$!', $vars['logo'])) {
125 $vars['logo'] = base_path() . $logo;
126 }
127 }
128
129 /**
130 * Retrieve the color.module info for a particular theme.
131 */
132 function color_get_info($theme) {
133 $path = drupal_get_path('theme', $theme);
134 $file = DRUPAL_ROOT . '/' . $path . '/color/color.inc';
135 if ($path && file_exists($file)) {
136 include $file;
137 return $info;
138 }
139 }
140
141 /**
142 * Helper function to retrieve the color palette for a particular theme.
143 */
144 function color_get_palette($theme, $default = FALSE) {
145 // Fetch and expand default palette.
146 $fields = array('base', 'link', 'top', 'bottom', 'text');
147 $info = color_get_info($theme);
148 $keys = array_keys($info['schemes']);
149 foreach (explode(',', array_shift($keys)) as $k => $scheme) {
150 $palette[$fields[$k]] = $scheme;
151 }
152
153 // Load variable.
154 return $default ? $palette : variable_get('color_' . $theme . '_palette', $palette);
155 }
156
157 /**
158 * Form callback. Returns the configuration form.
159 */
160 function color_scheme_form(&$form_state, $theme) {
161 $base = drupal_get_path('module', 'color');
162 $info = color_get_info($theme);
163
164 // Add Farbtastic color picker.
165 drupal_add_library('system', 'farbtastic');
166
167 // Add custom CSS and JS.
168 drupal_add_css($base . '/color.css', array('preprocess' => FALSE));
169 drupal_add_js($base . '/color.js');
170 drupal_add_js(array('color' => array(
171 'reference' => color_get_palette($theme, TRUE)
172 )), 'setting');
173
174 // See if we're using a predefined scheme.
175 $current = implode(',', variable_get('color_' . $theme . '_palette', array()));
176 // Note: we use the original theme when the default scheme is chosen.
177 $current = isset($info['schemes'][$current]) ? $current : ($current == '' ? reset($info['schemes']) : '');
178
179 // Add scheme selector.
180 $info['schemes'][''] = t('Custom');
181 $form['scheme'] = array(
182 '#type' => 'select',
183 '#title' => t('Color set'),
184 '#options' => $info['schemes'],
185 '#default_value' => $current,
186 );
187
188 // Add palette fields.
189 $palette = color_get_palette($theme);
190 $names = array(
191 'base' => t('Base color'),
192 'link' => t('Link color'),
193 'top' => t('Header top'),
194 'bottom' => t('Header bottom'),
195 'text' => t('Text color'),
196 );
197 $form['palette']['#tree'] = TRUE;
198 foreach ($palette as $name => $value) {
199 $form['palette'][$name] = array(
200 '#type' => 'textfield',
201 '#title' => $names[$name],
202 '#default_value' => $value,
203 '#size' => 8,
204 );
205 }
206 $form['theme'] = array('#type' => 'value', '#value' => arg(4));
207 $form['info'] = array('#type' => 'value', '#value' => $info);
208
209 return $form;
210 }
211
212 /**
213 * Theme the color form.
214 *
215 * @ingroup themeable
216 */
217 function theme_color_scheme_form($form) {
218 $theme = $form['theme']['#value'];
219 $info = $form['info']['#value'];
220 $path = drupal_get_path('theme', $theme) . '/';
221 drupal_add_css($path . $info['preview_css']);
222
223 $output = '';
224 $output .= '<div class="color-form clearfix">';
225 // Color schemes
226 $output .= drupal_render($form['scheme']);
227 // Palette
228 $output .= '<div id="palette" class="clearfix">';
229 foreach (element_children($form['palette']) as $name) {
230 $output .= drupal_render($form['palette'][$name]);
231 }
232 $output .= '</div>';
233 // Preview
234 $output .= drupal_render_children($form);
235 $output .= '<h2>' . t('Preview') . '</h2>';
236 $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>';
237 // Close the wrapper div.
238 $output .= '</div>';
239
240 return $output;
241 }
242
243 /**
244 * Submit handler for color change form.
245 */
246 function color_scheme_form_submit($form, &$form_state) {
247 // Get theme coloring info.
248 if (!isset($form_state['values']['info'])) {
249 return;
250 }
251 $theme = $form_state['values']['theme'];
252 $info = $form_state['values']['info'];
253
254 // Resolve palette.
255 $palette = $form_state['values']['palette'];
256 if ($form_state['values']['scheme'] != '') {
257 $scheme = explode(',', $form_state['values']['scheme']);
258 foreach ($palette as $k => $color) {
259 $palette[$k] = array_shift($scheme);
260 }
261 }
262
263 // Make sure enough memory is available, if PHP's memory limit is compiled in.
264 if (function_exists('memory_get_usage')) {
265 // Fetch source image dimensions.
266 $source = drupal_get_path('theme', $theme) . '/' . $info['base_image'];
267 list($width, $height) = getimagesize($source);
268
269 // We need at least a copy of the source and a target buffer of the same
270 // size (both at 32bpp).
271 $required = $width * $height * 8;
272 $usage = memory_get_usage();
273 $limit = parse_size(ini_get('memory_limit'));
274 if ($usage + $required > $limit) {
275 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/ini.core.php#ini.sect.resource-limits')), 'error');
276 return;
277 }
278 }
279
280 // Delete old files.
281 foreach (variable_get('color_' . $theme . '_files', array()) as $file) {
282 @unlink($file);
283 }
284 if (isset($file) && $file = dirname($file)) {
285 @rmdir($file);
286 }
287
288 // Don't render the default colorscheme, use the standard theme instead.
289 if (implode(',', color_get_palette($theme, TRUE)) == implode(',', $palette)) {
290 variable_del('color_' . $theme . '_palette');
291 variable_del('color_' . $theme . '_stylesheets');
292 variable_del('color_' . $theme . '_logo');
293 variable_del('color_' . $theme . '_files');
294 variable_del('color_' . $theme . '_screenshot');
295 return;
296 }
297
298 // Prepare target locations for generated files.
299 $id = $theme . '-' . substr(md5(serialize($palette) . microtime()), 0, 8);
300 $paths['color'] = file_directory_path() . '/color';
301 $paths['target'] = $paths['color'] . '/' . $id;
302 foreach ($paths as $path) {
303 file_check_directory($path, FILE_CREATE_DIRECTORY);
304 }
305 $paths['target'] = $paths['target'] . '/';
306 $paths['id'] = $id;
307 $paths['source'] = drupal_get_path('theme', $theme) . '/';
308 $paths['files'] = $paths['map'] = array();
309
310 // Save palette and logo location.
311 variable_set('color_' . $theme . '_palette', $palette);
312 variable_set('color_' . $theme . '_logo', $paths['target'] . 'logo.png');
313
314 // Copy over neutral images.
315 foreach ($info['copy'] as $file) {
316 $base = basename($file);
317 $source = $paths['source'] . $file;
318 $filepath = file_unmanaged_copy($source, $paths['target'] . $base);
319 $paths['map'][$file] = $base;
320 $paths['files'][] = $filepath;
321 }
322
323 // Render new images, if image has been provided.
324 if ($info['base_image']) {
325 _color_render_images($theme, $info, $paths, $palette);
326 }
327
328 // Rewrite theme stylesheets.
329 $css = array();
330 foreach ($info['css'] as $stylesheet) {
331 // Build a temporary array with LTR and RTL files.
332 $files = array();
333 if (file_exists($paths['source'] . $stylesheet)) {
334 $files[] = $stylesheet;
335
336 $rtl_file = str_replace('.css', '-rtl.css', $stylesheet);
337 if (file_exists($paths['source'] . $rtl_file)) {
338 $files[] = $rtl_file;
339 }
340 }
341
342 foreach ($files as $file) {
343 // Aggregate @imports recursively for each configured top level CSS file
344 // without optimization. Aggregation and optimization will be
345 // handled by drupal_build_css_cache() only.
346 $style = drupal_load_stylesheet($paths['source'] . $file, FALSE);
347
348 // Return the path to where this CSS file originated from, stripping
349 // off the name of the file at the end of the path.
350 $base = base_path() . dirname($paths['source'] . $file) . '/';
351 _drupal_build_css_path(NULL, $base);
352
353 // Prefix all paths within this CSS file, ignoring absolute paths.
354 $style = preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $style);
355
356 // Rewrite stylesheet with new colors.
357 $style = _color_rewrite_stylesheet($theme, $info, $paths, $palette, $style);
358 $base_file = basename($file);
359 $css[] = $paths['target'] . $base_file;
360 _color_save_stylesheet($paths['target'] . $base_file, $style, $paths);
361 }
362 }
363
364 // Maintain list of files.
365 variable_set('color_' . $theme . '_stylesheets', $css);
366 variable_set('color_' . $theme . '_files', $paths['files']);
367 }
368
369 /**
370 * Rewrite the stylesheet to match the colors in the palette.
371 */
372 function _color_rewrite_stylesheet($theme, &$info, &$paths, $palette, $style) {
373 $themes = list_themes();
374 // Prepare color conversion table.
375 $conversion = $palette;
376 unset($conversion['base']);
377 foreach ($conversion as $k => $v) {
378 $conversion[$k] = drupal_strtolower($v);
379 }
380 $default = color_get_palette($theme, TRUE);
381
382 // Split off the "Don't touch" section of the stylesheet.
383 $split = "Color Module: Don't touch";
384 if (strpos($style, $split) !== FALSE) {
385 list($style, $fixed) = explode($split, $style);
386 }
387
388 // Find all colors in the stylesheet and the chunks in between.
389 $style = preg_split('/(#[0-9a-f]{6}|#[0-9a-f]{3})/i', $style, -1, PREG_SPLIT_DELIM_CAPTURE);
390 $is_color = FALSE;
391 $output = '';
392 $base = 'base';
393
394 // Iterate over all the parts.
395 foreach ($style as $chunk) {
396 if ($is_color) {
397 $chunk = drupal_strtolower($chunk);
398 // Check if this is one of the colors in the default palette.
399 if ($key = array_search($chunk, $default)) {
400 $chunk = $conversion[$key];
401 }
402 // Not a pre-set color. Extrapolate from the base.
403 else {
404 $chunk = _color_shift($palette[$base], $default[$base], $chunk, $info['blend_target']);
405 }
406 }
407 else {
408 // Determine the most suitable base color for the next color.
409
410 // 'a' declarations. Use link.
411 if (preg_match('@[^a-z0-9_-](a)[^a-z0-9_-][^/{]*{[^{]+$@i', $chunk)) {
412 $base = 'link';
413 }
414 // 'color:' styles. Use text.
415 elseif (preg_match('/(?<!-)color[^{:]*:[^{#]*$/i', $chunk)) {
416 $base = 'text';
417 }
418 // Reset back to base.
419 else {
420 $base = 'base';
421 }
422 }
423 $output .= $chunk;
424 $is_color = !$is_color;
425 }
426 // Append fixed colors segment.
427 if (isset($fixed)) {
428 $output .= $fixed;
429 }
430
431 // Replace paths to images.
432 foreach ($paths['map'] as $before => $after) {
433 $before = base_path() . $paths['source'] . $before;
434 $before = preg_replace('`(^|/)(?!../)([^/]+)/../`', '$1', $before);
435 $output = str_replace($before, $after, $output);
436 }
437
438 return $output;
439 }
440
441 /**
442 * Save the rewritten stylesheet to disk.
443 */
444 function _color_save_stylesheet($file, $style, &$paths) {
445 $filepath = file_unmanaged_save_data($style, $file, FILE_EXISTS_REPLACE);
446 $paths['files'][] = $filepath;
447
448 // Set standard file permissions for webserver-generated files.
449 drupal_chmod($file);
450 }
451
452 /**
453 * Render images that match a given palette.
454 */
455 function _color_render_images($theme, &$info, &$paths, $palette) {
456
457 // Prepare template image.
458 $source = $paths['source'] . '/' . $info['base_image'];
459 $source = imagecreatefrompng($source);
460 $width = imagesx($source);
461 $height = imagesy($source);
462
463 // Prepare target buffer.
464 $target = imagecreatetruecolor($width, $height);
465 imagealphablending($target, TRUE);
466
467 // Fill regions of solid color.
468 foreach ($info['fill'] as $color => $fill) {
469 imagefilledrectangle($target, $fill[0], $fill[1], $fill[0] + $fill[2], $fill[1] + $fill[3], _color_gd($target, $palette[$color]));
470 }
471
472 // Render gradient.
473 for ($y = 0; $y < $info['gradient'][3]; ++$y) {
474 $color = _color_blend($target, $palette['top'], $palette['bottom'], $y / ($info['gradient'][3] - 1));
475 imagefilledrectangle($target, $info['gradient'][0], $info['gradient'][1] + $y, $info['gradient'][0] + $info['gradient'][2], $info['gradient'][1] + $y + 1, $color);
476 }
477
478 // Blend over template.
479 imagecopy($target, $source, 0, 0, 0, 0, $width, $height);
480
481 // Clean up template image.
482 imagedestroy($source);
483
484 // Cut out slices.
485 foreach ($info['slices'] as $file => $coord) {
486 list($x, $y, $width, $height) = $coord;
487 $base = basename($file);
488 $image = $paths['target'] . $base;
489
490 // Cut out slice.
491 if ($file == 'screenshot.png') {
492 $slice = imagecreatetruecolor(150, 90);
493 imagecopyresampled($slice, $target, 0, 0, $x, $y, 150, 90, $width, $height);
494 variable_set('color_' . $theme . '_screenshot', $image);
495 }
496 else {
497 $slice = imagecreatetruecolor($width, $height);
498 imagecopy($slice, $target, 0, 0, $x, $y, $width, $height);
499 }
500
501 // Save image.
502 imagepng($slice, $image);
503 imagedestroy($slice);
504 $paths['files'][] = $image;
505
506 // Set standard file permissions for webserver-generated files
507 drupal_chmod($image);
508
509 // Build before/after map of image paths.
510 $paths['map'][$file] = $base;
511 }
512
513 // Clean up target buffer.
514 imagedestroy($target);
515 }
516
517 /**
518 * Shift a given color, using a reference pair and a target blend color.
519 *
520 * Note: this function is significantly different from the JS version, as it
521 * is written to match the blended images perfectly.
522 *
523 * Constraint: if (ref2 == target + (ref1 - target) * delta) for some fraction delta
524 * then (return == target + (given - target) * delta)
525 *
526 * Loose constraint: Preserve relative positions in saturation and luminance
527 * space.
528 */
529 function _color_shift($given, $ref1, $ref2, $target) {
530 // We assume that ref2 is a blend of ref1 and target and find
531 // delta based on the length of the difference vectors.
532
533 // delta = 1 - |ref2 - ref1| / |white - ref1|
534 $target = _color_unpack($target, TRUE);
535 $ref1 = _color_unpack($ref1, TRUE);
536 $ref2 = _color_unpack($ref2, TRUE);
537 $numerator = 0;
538 $denominator = 0;
539 for ($i = 0; $i < 3; ++$i) {
540 $numerator += ($ref2[$i] - $ref1[$i]) * ($ref2[$i] - $ref1[$i]);
541 $denominator += ($target[$i] - $ref1[$i]) * ($target[$i] - $ref1[$i]);
542 }
543 $delta = ($denominator > 0) ? (1 - sqrt($numerator / $denominator)) : 0;
544
545 // Calculate the color that ref2 would be if the assumption was true.
546 for ($i = 0; $i < 3; ++$i) {
547 $ref3[$i] = $target[$i] + ($ref1[$i] - $target[$i]) * $delta;
548 }
549
550 // If the assumption is not true, there is a difference between ref2 and ref3.
551 // We measure this in HSL space. Notation: x' = hsl(x).
552 $ref2 = _color_rgb2hsl($ref2);
553 $ref3 = _color_rgb2hsl($ref3);
554 for ($i = 0; $i < 3; ++$i) {
555 $shift[$i] = $ref2[$i] - $ref3[$i];
556 }
557
558 // Take the given color, and blend it towards the target.
559 $given = _color_unpack($given, TRUE);
560 for ($i = 0; $i < 3; ++$i) {
561 $result[$i] = $target[$i] + ($given[$i] - $target[$i]) * $delta;
562 }
563
564 // Finally, we apply the extra shift in HSL space.
565 // Note: if ref2 is a pure blend of ref1 and target, then |shift| = 0.
566 $result = _color_rgb2hsl($result);
567 for ($i = 0; $i < 3; ++$i) {
568 $result[$i] = min(1, max(0, $result[$i] + $shift[$i]));
569 }
570 $result = _color_hsl2rgb($result);
571
572 // Return hex color.
573 return _color_pack($result, TRUE);
574 }
575
576 /**
577 * Convert a hex triplet into a GD color.
578 */
579 function _color_gd($img, $hex) {
580 $c = array_merge(array($img), _color_unpack($hex));
581 return call_user_func_array('imagecolorallocate', $c);
582 }
583
584 /**
585 * Blend two hex colors and return the GD color.
586 */
587 function _color_blend($img, $hex1, $hex2, $alpha) {
588 $in1 = _color_unpack($hex1);
589 $in2 = _color_unpack($hex2);
590 $out = array($img);
591 for ($i = 0; $i < 3; ++$i) {
592 $out[] = $in1[$i] + ($in2[$i] - $in1[$i]) * $alpha;
593 }
594
595 return call_user_func_array('imagecolorallocate', $out);
596 }
597
598 /**
599 * Convert a hex color into an RGB triplet.
600 */
601 function _color_unpack($hex, $normalize = FALSE) {
602 if (strlen($hex) == 4) {
603 $hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3];
604 }
605 $c = hexdec($hex);
606 for ($i = 16; $i >= 0; $i -= 8) {
607 $out[] = (($c >> $i) & 0xFF) / ($normalize ? 255 : 1);
608 }
609
610 return $out;
611 }
612
613 /**
614 * Convert an RGB triplet to a hex color.
615 */
616 function _color_pack($rgb, $normalize = FALSE) {
617 $out = 0;
618 foreach ($rgb as $k => $v) {
619 $out |= (($v * ($normalize ? 255 : 1)) << (16 - $k * 8));
620 }
621
622 return '#' . str_pad(dechex($out), 6, 0, STR_PAD_LEFT);
623 }
624
625 /**
626 * Convert a HSL triplet into RGB.
627 */
628 function _color_hsl2rgb($hsl) {
629 $h = $hsl[0];
630 $s = $hsl[1];
631 $l = $hsl[2];
632 $m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l*$s;
633 $m1 = $l * 2 - $m2;
634
635 return array(
636 _color_hue2rgb($m1, $m2, $h + 0.33333),
637 _color_hue2rgb($m1, $m2, $h),
638 _color_hue2rgb($m1, $m2, $h - 0.33333),
639 );
640 }
641
642 /**
643 * Helper function for _color_hsl2rgb().
644 */
645 function _color_hue2rgb($m1, $m2, $h) {
646 $h = ($h < 0) ? $h + 1 : (($h > 1) ? $h - 1 : $h);
647 if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6;
648 if ($h * 2 < 1) return $m2;
649 if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (0.66666 - $h) * 6;
650
651 return $m1;
652 }
653
654 /**
655 * Convert an RGB triplet to HSL.
656 */
657 function _color_rgb2hsl($rgb) {
658 $r = $rgb[0];
659 $g = $rgb[1];
660 $b = $rgb[2];
661 $min = min($r, min($g, $b));
662 $max = max($r, max($g, $b));
663 $delta = $max - $min;
664 $l = ($min + $max) / 2;
665 $s = 0;
666
667 if ($l > 0 && $l < 1) {
668 $s = $delta / ($l < 0.5 ? (2 * $l) : (2 - 2 * $l));
669 }
670
671 $h = 0;
672 if ($delta > 0) {
673 if ($max == $r && $max != $g) $h += ($g - $b) / $delta;
674 if ($max == $g && $max != $b) $h += (2 + ($b - $r) / $delta);
675 if ($max == $b && $max != $r) $h += (4 + ($r - $g) / $delta);
676 $h /= 6;
677 }
678
679 return array($h, $s, $l);
680 }
681

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.