Projet

Général

Profil

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

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

1 85ad3d82 Assos Assos
<?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 6b24a280 Assos Assos
  $hex = substr($hex, 1);
738
  if (strlen($hex) == 3) {
739
    $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
740 85ad3d82 Assos Assos
  }
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
}