Projet

Général

Profil

Paste
Télécharger (27 ko) Statistiques
| Branche: | Révision:

root / drupal7 / modules / color / color.module @ c7768a53

1
<?php
2
/**
3
 * @file
4
 * Allows users to change the color scheme of themes.
5
 */
6

    
7
/**
8
 * Implements hook_help().
9
 */
10
function color_help($path, $arg) {
11
  switch ($path) {
12
    case 'admin/help#color':
13
      $output = '<h3>' . t('About') . '</h3>';
14
      $output .= '<p>' . t('The Color module allows users with the <em>Administer site configuration</em> permission to quickly and easily change the color scheme of themes that have been built to be compatible with it. For more information, see the online handbook entry for <a href="@color">Color module</a>.', array('@color' => 'http://drupal.org/documentation/modules/color')) . '</p>';
15
      $output .= '<h3>' . t('Uses') . '</h3>';
16
      $output .= '<dl>';
17
      $output .= '<dt>' . t('Changing colors') . '</dt>';
18
      $output .= '<dd>' . t("Using the Color module allows you to easily change the color of links, backgrounds, text, and other theme elements. To change the color settings for a compatible theme, select the <em>Settings</em> link for your theme on the <a href='@configure'>Themes administration page</a>. If you don't see a color picker on that page, then your theme is not compatible with the color module. If you are sure that the theme does indeed support the color module, but the color picker does not appear, then <a href='@troubleshoot'>follow these troubleshooting procedures</a>.", array('@configure' => url('admin/appearance'), '@troubleshoot' => 'http://drupal.org/node/109457')) . '</dd>';
19
      $output .= '<dd>' . t("The 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, <em>you must save your color settings again, even if they haven't changed</em>. This step is required because the module stylesheets (in the files directory) need to be recreated to include your changes.") . '</dd>';
20
      $output .= '</dl>';
21
      return $output;
22
  }
23
}
24

    
25
/**
26
 * Implements hook_theme().
27
 */
28
function color_theme() {
29
  return array(
30
    'color_scheme_form' => array(
31
      'render element' => 'form',
32
    ),
33
  );
34
}
35

    
36
/**
37
 * Implements hook_form_FORM_ID_alter().
38
 */
39
function color_form_system_theme_settings_alter(&$form, &$form_state) {
40
  if (isset($form_state['build_info']['args'][0]) && ($theme = $form_state['build_info']['args'][0]) && color_get_info($theme) && function_exists('gd_info')) {
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, $form_state, $theme);
49
    $form['#validate'][] = 'color_scheme_form_validate';
50
    $form['#submit'][] = 'color_scheme_form_submit';
51
  }
52
}
53

    
54
/**
55
 * Implements 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', array('path' => $screenshot, 'title' => '', 'attributes' => array('class' => array('screenshot'))));
71
      }
72
    }
73
  }
74
}
75

    
76
/**
77
 * Replaces style sheets with color-altered style sheets.
78
 *
79
 * A theme that supports the color module should call this function from its
80
 * THEME_process_html() function, so that the correct style sheets are
81
 * included when html.tpl.php is rendered.
82
 *
83
 * @see theme()
84
 */
85
function _color_html_alter(&$vars) {
86
  global $theme_key;
87
  $themes = list_themes();
88

    
89
  // Override stylesheets.
90
  $color_paths = variable_get('color_' . $theme_key . '_stylesheets', array());
91
  if (!empty($color_paths)) {
92

    
93
    foreach ($themes[$theme_key]->stylesheets['all'] as $base_filename => $old_path) {
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 (drupal_basename($old_path) == drupal_basename($color_path)) {
100
          // Replace the path to the new css file.
101
          // This keeps the order of the stylesheets intact.
102
          $vars['css'][$old_path]['data'] = $color_path;
103
        }
104
      }
105
    }
106

    
107
    $vars['styles'] = drupal_get_css($vars['css']);
108
  }
109
}
110

    
111
/**
112
 * Replaces the logo with a color-altered logo.
113
 *
114
 * A theme that supports the color module should call this function from its
115
 * THEME_process_page() function, so that the correct logo is included when
116
 * page.tpl.php is rendered.
117
 *
118
 * @see theme()
119
 */
120
function _color_page_alter(&$vars) {
121
  global $theme_key;
122

    
123
  // Override logo.
124
  $logo = variable_get('color_' . $theme_key . '_logo');
125
  if ($logo && $vars['logo'] && preg_match('!' . $theme_key . '/logo.png$!', $vars['logo'])) {
126
    $vars['logo'] = file_create_url($logo);
127
  }
128
}
129

    
130
/**
131
 * Retrieves the Color module information for a particular theme.
132
 */
133
function color_get_info($theme) {
134
  static $theme_info = array();
135

    
136
  if (isset($theme_info[$theme])) {
137
    return $theme_info[$theme];
138
  }
139

    
140
  $path = drupal_get_path('theme', $theme);
141
  $file = DRUPAL_ROOT . '/' . $path . '/color/color.inc';
142
  if ($path && file_exists($file)) {
143
    include $file;
144
    $theme_info[$theme] = $info;
145
    return $info;
146
  }
147
}
148

    
149
/**
150
 * Retrieves the color palette for a particular theme.
151
 */
152
function color_get_palette($theme, $default = FALSE) {
153
  // Fetch and expand default palette.
154
  $info = color_get_info($theme);
155
  $palette = $info['schemes']['default']['colors'];
156

    
157
  // Load variable.
158
  return $default ? $palette : variable_get('color_' . $theme . '_palette', $palette);
159
}
160

    
161
/**
162
 * Form constructor for the color configuration form for a particular theme.
163
 *
164
 * @param $theme
165
 *   The machine name of the theme whose color settings are being configured.
166
 *
167
 * @see color_scheme_form_validate()
168
 * @see color_scheme_form_submit()
169
 * @ingroup forms
170
 */
171
function color_scheme_form($complete_form, &$form_state, $theme) {
172
  $base = drupal_get_path('module', 'color');
173
  $info = color_get_info($theme);
174

    
175
  $info['schemes'][''] = array('title' => t('Custom'), 'colors' => array());
176
  $color_sets = array();
177
  $schemes = array();
178
  foreach ($info['schemes'] as $key => $scheme) {
179
    $color_sets[$key] = $scheme['title'];
180
    $schemes[$key] = $scheme['colors'];
181
    $schemes[$key] += $info['schemes']['default']['colors'];
182
  }
183

    
184
  // See if we're using a predefined scheme.
185
  // Note: we use the original theme when the default scheme is chosen.
186
  $current_scheme = variable_get('color_' . $theme . '_palette', array());
187
  foreach ($schemes as $key => $scheme) {
188
    if ($current_scheme == $scheme) {
189
      $scheme_name = $key;
190
      break;
191
    }
192
  }
193
  if (empty($scheme_name)) {
194
    if (empty($current_scheme)) {
195
      $scheme_name = 'default';
196
    }
197
    else {
198
      $scheme_name = '';
199
    }
200
  }
201

    
202
  // Add scheme selector.
203
  $form['scheme'] = array(
204
    '#type' => 'select',
205
    '#title' => t('Color set'),
206
    '#options' => $color_sets,
207
    '#default_value' => $scheme_name,
208
    '#attached' => array(
209
      // Add Farbtastic color picker.
210
      'library' => array(
211
        array('system', 'farbtastic'),
212
      ),
213
      // Add custom CSS.
214
      'css' => array(
215
        $base . '/color.css' => array(),
216
      ),
217
      // Add custom JavaScript.
218
      'js' => array(
219
        $base . '/color.js',
220
        array(
221
          'data' => array(
222
            'color' => array(
223
              'reference' => color_get_palette($theme, TRUE),
224
              'schemes' => $schemes,
225
            ),
226
            'gradients' => $info['gradients'],
227
          ),
228
          'type' => 'setting',
229
        ),
230
      ),
231
    ),
232
  );
233

    
234
  // Add palette fields.
235
  $palette = color_get_palette($theme);
236
  $names = $info['fields'];
237
  $form['palette']['#tree'] = TRUE;
238
  foreach ($palette as $name => $value) {
239
    if (isset($names[$name])) {
240
      $form['palette'][$name] = array(
241
        '#type' => 'textfield',
242
        '#title' => check_plain($names[$name]),
243
        '#value_callback' => 'color_palette_color_value',
244
        '#default_value' => $value,
245
        '#size' => 8,
246
      );
247
    }
248
  }
249
  $form['theme'] = array('#type' => 'value', '#value' => $theme);
250
  $form['info'] = array('#type' => 'value', '#value' => $info);
251

    
252
  return $form;
253
}
254

    
255
/**
256
 * Returns HTML for a theme's color form.
257
 *
258
 * @param $variables
259
 *   An associative array containing:
260
 *   - form: A render element representing the form.
261
 *
262
 * @ingroup themeable
263
 */
264
function theme_color_scheme_form($variables) {
265
  $form = $variables['form'];
266

    
267
  $theme = $form['theme']['#value'];
268
  $info = $form['info']['#value'];
269
  $path = drupal_get_path('theme', $theme) . '/';
270
  drupal_add_css($path . $info['preview_css']);
271

    
272
  $preview_js_path = isset($info['preview_js']) ? $path . $info['preview_js'] : drupal_get_path('module', 'color') . '/' . 'preview.js';
273
  // Add the JS at a weight below color.js.
274
  drupal_add_js($preview_js_path, array('weight' => -1));
275

    
276
  $output  = '';
277
  $output .= '<div class="color-form clearfix">';
278
  // Color schemes
279
  $output .= drupal_render($form['scheme']);
280
  // Palette
281
  $output .= '<div id="palette" class="clearfix">';
282
  foreach (element_children($form['palette']) as $name) {
283
    $output .= drupal_render($form['palette'][$name]);
284
  }
285
  $output .= '</div>';
286
  // Preview
287
  $output .= drupal_render_children($form);
288
  $output .= '<h2>' . t('Preview') . '</h2>';
289
  // Attempt to load preview HTML if the theme provides it.
290
  $preview_html_path = DRUPAL_ROOT . '/' . (isset($info['preview_html']) ? drupal_get_path('theme', $theme) . '/' . $info['preview_html'] : drupal_get_path('module', 'color') . '/preview.html');
291
  $output .= file_get_contents($preview_html_path);
292
  // Close the wrapper div.
293
  $output .= '</div>';
294

    
295
  return $output;
296
}
297

    
298
/**
299
 * Determines the value for a palette color field.
300
 *
301
 * @param $element
302
 *   The form element whose value is being populated.
303
 * @param $input
304
 *   The incoming input to populate the form element. If this is FALSE,
305
 *   the element's default value should be returned.
306
 * @param $form_state
307
 *   A keyed array containing the current state of the form.
308
 *
309
 * @return
310
 *   The data that will appear in the $form_state['values'] collection for this
311
 *   element. Return nothing to use the default.
312
 */
313
function color_palette_color_value($element, $input = FALSE, $form_state = array()) {
314
  // If we suspect a possible cross-site request forgery attack, only accept
315
  // hexadecimal CSS color strings from user input, to avoid problems when this
316
  // value is used in the JavaScript preview.
317
  if ($input !== FALSE) {
318
    // Start with the provided value for this textfield, and validate that if
319
    // necessary, falling back on the default value.
320
    $value = form_type_textfield_value($element, $input, $form_state);
321
    if (!$value || !isset($form_state['complete form']['#token']) || color_valid_hexadecimal_string($value) || drupal_valid_token($form_state['values']['form_token'], $form_state['complete form']['#token'])) {
322
      return $value;
323
    }
324
    else {
325
      return $element['#default_value'];
326
    }
327
  }
328
}
329

    
330
/**
331
 * Determines if a hexadecimal CSS color string is valid.
332
 *
333
 * @param $color
334
 *   The string to check.
335
 *
336
 * @return
337
 *   TRUE if the string is a valid hexadecimal CSS color string, or FALSE if it
338
 *   isn't.
339
 */
340
function color_valid_hexadecimal_string($color) {
341
  return preg_match('/^#([a-f0-9]{3}){1,2}$/iD', $color);
342
}
343

    
344
/**
345
 * Form validation handler for color_scheme_form().
346
 *
347
 * @see color_scheme_form_submit()
348
 */
349
function color_scheme_form_validate($form, &$form_state) {
350
  // Only accept hexadecimal CSS color strings to avoid XSS upon use.
351
  foreach ($form_state['values']['palette'] as $key => $color) {
352
    if (!color_valid_hexadecimal_string($color)) {
353
      form_set_error('palette][' . $key, t('%name must be a valid hexadecimal CSS color value.', array('%name' => $form['color']['palette'][$key]['#title'])));
354
    }
355
  }
356
}
357

    
358
/**
359
 * Form submission handler for color_scheme_form().
360
 *
361
 * @see color_scheme_form_validate()
362
 */
363
function color_scheme_form_submit($form, &$form_state) {
364
  // Get theme coloring info.
365
  if (!isset($form_state['values']['info'])) {
366
    return;
367
  }
368
  $theme = $form_state['values']['theme'];
369
  $info = $form_state['values']['info'];
370

    
371
  // Resolve palette.
372
  $palette = $form_state['values']['palette'];
373
  if ($form_state['values']['scheme'] != '') {
374
    foreach ($palette as $key => $color) {
375
      if (isset($info['schemes'][$form_state['values']['scheme']]['colors'][$key])) {
376
        $palette[$key] = $info['schemes'][$form_state['values']['scheme']]['colors'][$key];
377
      }
378
    }
379
    $palette += $info['schemes']['default']['colors'];
380
  }
381

    
382
  // Make sure enough memory is available, if PHP's memory limit is compiled in.
383
  if (function_exists('memory_get_usage')) {
384
    // Fetch source image dimensions.
385
    $source = drupal_get_path('theme', $theme) . '/' . $info['base_image'];
386
    list($width, $height) = getimagesize($source);
387

    
388
    // We need at least a copy of the source and a target buffer of the same
389
    // size (both at 32bpp).
390
    $required = $width * $height * 8;
391
    // We intend to prevent color scheme changes if there isn't enough memory
392
    // available.  memory_get_usage(TRUE) returns a more accurate number than
393
    // memory_get_usage(), therefore we won't inadvertently reject a color
394
    // scheme change based on a faulty memory calculation.
395
    $usage = memory_get_usage(TRUE);
396
    $memory_limit = ini_get('memory_limit');
397
    $size = parse_size($memory_limit);
398
    if (!drupal_check_memory_limit($usage + $required, $memory_limit)) {
399
      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 - $size), '@url' => 'http://www.php.net/manual/ini.core.php#ini.sect.resource-limits')), 'error');
400
      return;
401
    }
402
  }
403

    
404
  // Delete old files.
405
  foreach (variable_get('color_' . $theme . '_files', array()) as $file) {
406
    @drupal_unlink($file);
407
  }
408
  if (isset($file) && $file = dirname($file)) {
409
    @drupal_rmdir($file);
410
  }
411

    
412
  // Don't render the default colorscheme, use the standard theme instead.
413
  if (implode(',', color_get_palette($theme, TRUE)) == implode(',', $palette)) {
414
    variable_del('color_' . $theme . '_palette');
415
    variable_del('color_' . $theme . '_stylesheets');
416
    variable_del('color_' . $theme . '_logo');
417
    variable_del('color_' . $theme . '_files');
418
    variable_del('color_' . $theme . '_screenshot');
419
    return;
420
  }
421

    
422
  // Prepare target locations for generated files.
423
  $id = $theme . '-' . substr(hash('sha256', serialize($palette) . microtime()), 0, 8);
424
  $paths['color'] = 'public://color';
425
  $paths['target'] = $paths['color'] . '/' . $id;
426
  foreach ($paths as $path) {
427
    file_prepare_directory($path, FILE_CREATE_DIRECTORY);
428
  }
429
  $paths['target'] = $paths['target'] . '/';
430
  $paths['id'] = $id;
431
  $paths['source'] = drupal_get_path('theme', $theme) . '/';
432
  $paths['files'] = $paths['map'] = array();
433

    
434
  // Save palette and logo location.
435
  variable_set('color_' . $theme . '_palette', $palette);
436
  variable_set('color_' . $theme . '_logo', $paths['target'] . 'logo.png');
437

    
438
  // Copy over neutral images.
439
  foreach ($info['copy'] as $file) {
440
    $base = drupal_basename($file);
441
    $source = $paths['source'] . $file;
442
    $filepath = file_unmanaged_copy($source, $paths['target'] . $base);
443
    $paths['map'][$file] = $base;
444
    $paths['files'][] = $filepath;
445
  }
446

    
447
  // Render new images, if image has been provided.
448
  if ($info['base_image']) {
449
    _color_render_images($theme, $info, $paths, $palette);
450
  }
451

    
452
  // Rewrite theme stylesheets.
453
  $css = array();
454
  foreach ($info['css'] as $stylesheet) {
455
    // Build a temporary array with LTR and RTL files.
456
    $files = array();
457
    if (file_exists($paths['source'] . $stylesheet)) {
458
      $files[] = $stylesheet;
459

    
460
      $rtl_file = str_replace('.css', '-rtl.css', $stylesheet);
461
      if (file_exists($paths['source'] . $rtl_file)) {
462
        $files[] = $rtl_file;
463
      }
464
    }
465

    
466
    foreach ($files as $file) {
467
      // Aggregate @imports recursively for each configured top level CSS file
468
      // without optimization. Aggregation and optimization will be
469
      // handled by drupal_build_css_cache() only.
470
      $style = drupal_load_stylesheet($paths['source'] . $file, FALSE);
471

    
472
      // Return the path to where this CSS file originated from, stripping
473
      // off the name of the file at the end of the path.
474
      $base = base_path() . dirname($paths['source'] . $file) . '/';
475
      _drupal_build_css_path(NULL, $base);
476

    
477
      // Prefix all paths within this CSS file, ignoring absolute paths.
478
      $style = preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $style);
479

    
480
      // Rewrite stylesheet with new colors.
481
      $style = _color_rewrite_stylesheet($theme, $info, $paths, $palette, $style);
482
      $base_file = drupal_basename($file);
483
      $css[] = $paths['target'] . $base_file;
484
      _color_save_stylesheet($paths['target'] . $base_file, $style, $paths);
485
    }
486
  }
487

    
488
  // Maintain list of files.
489
  variable_set('color_' . $theme . '_stylesheets', $css);
490
  variable_set('color_' . $theme . '_files', $paths['files']);
491
}
492

    
493
/**
494
 * Rewrites the stylesheet to match the colors in the palette.
495
 */
496
function _color_rewrite_stylesheet($theme, &$info, &$paths, $palette, $style) {
497
  $themes = list_themes();
498
  // Prepare color conversion table.
499
  $conversion = $palette;
500
  foreach ($conversion as $k => $v) {
501
    $conversion[$k] = drupal_strtolower($v);
502
  }
503
  $default = color_get_palette($theme, TRUE);
504

    
505
  // Split off the "Don't touch" section of the stylesheet.
506
  $split = "Color Module: Don't touch";
507
  if (strpos($style, $split) !== FALSE) {
508
    list($style, $fixed) = explode($split, $style);
509
  }
510

    
511
  // Find all colors in the stylesheet and the chunks in between.
512
  $style = preg_split('/(#[0-9a-f]{6}|#[0-9a-f]{3})/i', $style, -1, PREG_SPLIT_DELIM_CAPTURE);
513
  $is_color = FALSE;
514
  $output = '';
515
  $base = 'base';
516

    
517
  // Iterate over all the parts.
518
  foreach ($style as $chunk) {
519
    if ($is_color) {
520
      $chunk = drupal_strtolower($chunk);
521
      // Check if this is one of the colors in the default palette.
522
      if ($key = array_search($chunk, $default)) {
523
        $chunk = $conversion[$key];
524
      }
525
      // Not a pre-set color. Extrapolate from the base.
526
      else {
527
        $chunk = _color_shift($palette[$base], $default[$base], $chunk, $info['blend_target']);
528
      }
529
    }
530
    else {
531
      // Determine the most suitable base color for the next color.
532

    
533
      // 'a' declarations. Use link.
534
      if (preg_match('@[^a-z0-9_-](a)[^a-z0-9_-][^/{]*{[^{]+$@i', $chunk)) {
535
        $base = 'link';
536
      }
537
      // 'color:' styles. Use text.
538
      elseif (preg_match('/(?<!-)color[^{:]*:[^{#]*$/i', $chunk)) {
539
        $base = 'text';
540
      }
541
      // Reset back to base.
542
      else {
543
        $base = 'base';
544
      }
545
    }
546
    $output .= $chunk;
547
    $is_color = !$is_color;
548
  }
549
  // Append fixed colors segment.
550
  if (isset($fixed)) {
551
    $output .= $fixed;
552
  }
553

    
554
  // Replace paths to images.
555
  foreach ($paths['map'] as $before => $after) {
556
    $before = base_path() . $paths['source'] . $before;
557
    $before = preg_replace('`(^|/)(?!../)([^/]+)/../`', '$1', $before);
558
    $output = str_replace($before, $after, $output);
559
  }
560

    
561
  return $output;
562
}
563

    
564
/**
565
 * Saves the rewritten stylesheet to disk.
566
 */
567
function _color_save_stylesheet($file, $style, &$paths) {
568
  $filepath = file_unmanaged_save_data($style, $file, FILE_EXISTS_REPLACE);
569
  $paths['files'][] = $filepath;
570

    
571
  // Set standard file permissions for webserver-generated files.
572
  drupal_chmod($file);
573
}
574

    
575
/**
576
 * Renders images that match a given palette.
577
 */
578
function _color_render_images($theme, &$info, &$paths, $palette) {
579
  // Prepare template image.
580
  $source = $paths['source'] . '/' . $info['base_image'];
581
  $source = imagecreatefrompng($source);
582
  $width = imagesx($source);
583
  $height = imagesy($source);
584

    
585
  // Prepare target buffer.
586
  $target = imagecreatetruecolor($width, $height);
587
  imagealphablending($target, TRUE);
588

    
589
  // Fill regions of solid color.
590
  foreach ($info['fill'] as $color => $fill) {
591
    imagefilledrectangle($target, $fill[0], $fill[1], $fill[0] + $fill[2], $fill[1] + $fill[3], _color_gd($target, $palette[$color]));
592
  }
593

    
594
  // Render gradients.
595
  foreach ($info['gradients'] as $gradient) {
596
    // Get direction of the gradient.
597
    if (isset($gradient['direction']) && $gradient['direction'] == 'horizontal') {
598
      // Horizontal gradient.
599
      for ($x = 0; $x < $gradient['dimension'][2]; $x++) {
600
        $color = _color_blend($target, $palette[$gradient['colors'][0]], $palette[$gradient['colors'][1]], $x / ($gradient['dimension'][2] - 1));
601
        imagefilledrectangle($target, ($gradient['dimension'][0] + $x), $gradient['dimension'][1], ($gradient['dimension'][0] + $x + 1), ($gradient['dimension'][1] + $gradient['dimension'][3]), $color);
602
      }
603
    }
604
    else {
605
      // Vertical gradient.
606
      for ($y = 0; $y < $gradient['dimension'][3]; $y++) {
607
        $color = _color_blend($target, $palette[$gradient['colors'][0]], $palette[$gradient['colors'][1]], $y / ($gradient['dimension'][3] - 1));
608
        imagefilledrectangle($target, $gradient['dimension'][0], $gradient['dimension'][1] + $y, $gradient['dimension'][0] + $gradient['dimension'][2], $gradient['dimension'][1] + $y + 1, $color);
609
      }
610
    }
611
  }
612

    
613
  // Blend over template.
614
  imagecopy($target, $source, 0, 0, 0, 0, $width, $height);
615

    
616
  // Clean up template image.
617
  imagedestroy($source);
618

    
619
  // Cut out slices.
620
  foreach ($info['slices'] as $file => $coord) {
621
    list($x, $y, $width, $height) = $coord;
622
    $base = drupal_basename($file);
623
    $image = drupal_realpath($paths['target'] . $base);
624

    
625
    // Cut out slice.
626
    if ($file == 'screenshot.png') {
627
      $slice = imagecreatetruecolor(150, 90);
628
      imagecopyresampled($slice, $target, 0, 0, $x, $y, 150, 90, $width, $height);
629
      variable_set('color_' . $theme . '_screenshot', $image);
630
    }
631
    else {
632
      $slice = imagecreatetruecolor($width, $height);
633
      imagecopy($slice, $target, 0, 0, $x, $y, $width, $height);
634
    }
635

    
636
    // Save image.
637
    imagepng($slice, $image);
638
    imagedestroy($slice);
639
    $paths['files'][] = $image;
640

    
641
    // Set standard file permissions for webserver-generated files
642
    drupal_chmod($image);
643

    
644
    // Build before/after map of image paths.
645
    $paths['map'][$file] = $base;
646
  }
647

    
648
  // Clean up target buffer.
649
  imagedestroy($target);
650
}
651

    
652
/**
653
 * Shifts a given color, using a reference pair and a target blend color.
654
 *
655
 * Note: this function is significantly different from the JS version, as it
656
 * is written to match the blended images perfectly.
657
 *
658
 * Constraint: if (ref2 == target + (ref1 - target) * delta) for some fraction
659
 * delta then (return == target + (given - target) * delta).
660
 *
661
 * Loose constraint: Preserve relative positions in saturation and luminance
662
 * space.
663
 */
664
function _color_shift($given, $ref1, $ref2, $target) {
665
  // We assume that ref2 is a blend of ref1 and target and find
666
  // delta based on the length of the difference vectors.
667

    
668
  // delta = 1 - |ref2 - ref1| / |white - ref1|
669
  $target = _color_unpack($target, TRUE);
670
  $ref1 = _color_unpack($ref1, TRUE);
671
  $ref2 = _color_unpack($ref2, TRUE);
672
  $numerator = 0;
673
  $denominator = 0;
674
  for ($i = 0; $i < 3; ++$i) {
675
    $numerator += ($ref2[$i] - $ref1[$i]) * ($ref2[$i] - $ref1[$i]);
676
    $denominator += ($target[$i] - $ref1[$i]) * ($target[$i] - $ref1[$i]);
677
  }
678
  $delta = ($denominator > 0) ? (1 - sqrt($numerator / $denominator)) : 0;
679

    
680
  // Calculate the color that ref2 would be if the assumption was true.
681
  for ($i = 0; $i < 3; ++$i) {
682
    $ref3[$i] = $target[$i] + ($ref1[$i] - $target[$i]) * $delta;
683
  }
684

    
685
  // If the assumption is not true, there is a difference between ref2 and ref3.
686
  // We measure this in HSL space. Notation: x' = hsl(x).
687
  $ref2 = _color_rgb2hsl($ref2);
688
  $ref3 = _color_rgb2hsl($ref3);
689
  for ($i = 0; $i < 3; ++$i) {
690
    $shift[$i] = $ref2[$i] - $ref3[$i];
691
  }
692

    
693
  // Take the given color, and blend it towards the target.
694
  $given = _color_unpack($given, TRUE);
695
  for ($i = 0; $i < 3; ++$i) {
696
    $result[$i] = $target[$i] + ($given[$i] - $target[$i]) * $delta;
697
  }
698

    
699
  // Finally, we apply the extra shift in HSL space.
700
  // Note: if ref2 is a pure blend of ref1 and target, then |shift| = 0.
701
  $result = _color_rgb2hsl($result);
702
  for ($i = 0; $i < 3; ++$i) {
703
    $result[$i] = min(1, max(0, $result[$i] + $shift[$i]));
704
  }
705
  $result = _color_hsl2rgb($result);
706

    
707
  // Return hex color.
708
  return _color_pack($result, TRUE);
709
}
710

    
711
/**
712
 * Converts a hex triplet into a GD color.
713
 */
714
function _color_gd($img, $hex) {
715
  $c = array_merge(array($img), _color_unpack($hex));
716
  return call_user_func_array('imagecolorallocate', $c);
717
}
718

    
719
/**
720
 * Blends two hex colors and returns the GD color.
721
 */
722
function _color_blend($img, $hex1, $hex2, $alpha) {
723
  $in1 = _color_unpack($hex1);
724
  $in2 = _color_unpack($hex2);
725
  $out = array($img);
726
  for ($i = 0; $i < 3; ++$i) {
727
    $out[] = $in1[$i] + ($in2[$i] - $in1[$i]) * $alpha;
728
  }
729

    
730
  return call_user_func_array('imagecolorallocate', $out);
731
}
732

    
733
/**
734
 * Converts a hex color into an RGB triplet.
735
 */
736
function _color_unpack($hex, $normalize = FALSE) {
737
  $hex = substr($hex, 1);
738
  if (strlen($hex) == 3) {
739
    $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
740
  }
741
  $c = hexdec($hex);
742
  for ($i = 16; $i >= 0; $i -= 8) {
743
    $out[] = (($c >> $i) & 0xFF) / ($normalize ? 255 : 1);
744
  }
745

    
746
  return $out;
747
}
748

    
749
/**
750
 * Converts an RGB triplet to a hex color.
751
 */
752
function _color_pack($rgb, $normalize = FALSE) {
753
  $out = 0;
754
  foreach ($rgb as $k => $v) {
755
    $out |= (($v * ($normalize ? 255 : 1)) << (16 - $k * 8));
756
  }
757

    
758
  return '#' . str_pad(dechex($out), 6, 0, STR_PAD_LEFT);
759
}
760

    
761
/**
762
 * Converts an HSL triplet into RGB.
763
 */
764
function _color_hsl2rgb($hsl) {
765
  $h = $hsl[0];
766
  $s = $hsl[1];
767
  $l = $hsl[2];
768
  $m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l*$s;
769
  $m1 = $l * 2 - $m2;
770

    
771
  return array(
772
    _color_hue2rgb($m1, $m2, $h + 0.33333),
773
    _color_hue2rgb($m1, $m2, $h),
774
    _color_hue2rgb($m1, $m2, $h - 0.33333),
775
  );
776
}
777

    
778
/**
779
 * Helper function for _color_hsl2rgb().
780
 */
781
function _color_hue2rgb($m1, $m2, $h) {
782
  $h = ($h < 0) ? $h + 1 : (($h > 1) ? $h - 1 : $h);
783
  if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6;
784
  if ($h * 2 < 1) return $m2;
785
  if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (0.66666 - $h) * 6;
786

    
787
  return $m1;
788
}
789

    
790
/**
791
 * Converts an RGB triplet to HSL.
792
 */
793
function _color_rgb2hsl($rgb) {
794
  $r = $rgb[0];
795
  $g = $rgb[1];
796
  $b = $rgb[2];
797
  $min = min($r, min($g, $b));
798
  $max = max($r, max($g, $b));
799
  $delta = $max - $min;
800
  $l = ($min + $max) / 2;
801
  $s = 0;
802

    
803
  if ($l > 0 && $l < 1) {
804
    $s = $delta / ($l < 0.5 ? (2 * $l) : (2 - 2 * $l));
805
  }
806

    
807
  $h = 0;
808
  if ($delta > 0) {
809
    if ($max == $r && $max != $g) $h += ($g - $b) / $delta;
810
    if ($max == $g && $max != $b) $h += (2 + ($b - $r) / $delta);
811
    if ($max == $b && $max != $r) $h += (4 + ($r - $g) / $delta);
812
    $h /= 6;
813
  }
814

    
815
  return array($h, $s, $l);
816
}