You are here

fivestar.color.inc in Fivestar 6.2

File containing functions relating to the color widget.

File

includes/fivestar.color.inc
View source
<?php

/**
 * @file
 * File containing functions relating to the color widget.
 */

/**
 * Menu callback function for fivestar/preview/color.
 *
 * Outputs a dynamically generated star or cancel png.
 */
function fivestar_preview_color() {
  $args = func_get_args();

  // Remove query string if it gets passed in as argument.
  $filename = preg_replace('/\\?.*$/', '', array_pop($args));
  $type = array_pop($args);
  $widget = array_pop($args);

  // Convert args to our color scheme.
  $color_scheme = array(
    'on1' => $args[0],
    'on2' => $args[1],
    'hover1' => $args[2],
    'hover2' => $args[3],
    'off1' => $args[4],
    'off2' => $args[5],
    'matte' => $args[6],
  );

  // Find the source location of the desired widget.
  $widgets = module_invoke_all('fivestar_widgets');
  foreach ($widgets as $key => $name) {
    if (drupal_strtolower($name) == $widget) {
      $source_directory = str_replace($widget . '.css', '', $key);
      break;
    }
  }

  // Generate the requested image and exit.
  $image = _fivestar_color_render($source_directory . str_replace('.png', '-template.png', $filename), $color_scheme, $type);
  drupal_set_header('Content-type: image/png');
  drupal_set_header("Expires: " . gmdate("D, d M Y H:i:s", time() + 300) . " GMT");
  drupal_set_header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
  drupal_set_header("Cache-Control: max-age=300");
  imagepng($image);
  exit;
}

/**
 * Form callback. Returns the configuration form.
 */
function fivestar_color_form() {
  $form = array(
    '#tree' => FALSE,
    '#theme' => 'fivestar_color_form',
    '#type' => 'fieldset',
    '#title' => t('Color scheme'),
    '#weight' => -1,
    '#attributes' => array(
      'id' => 'fivestar_color_scheme_form',
    ),
    '#description' => t('A custom color widget must be selected to choose colors. Only the selected widget will be previewed.'),
  );
  $disabled = variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE;
  if ($disabled) {
    $form['#description'] = t('Custom colors are only supported when the <a href="!url">download method</a> is set to public.', array(
      '!url' => url('admin/settings/file-system'),
    ));
  }

  // Create the list of available schemes.
  $options = array(
    '#fff633,#f5d000,#d2e7f9,#027AC6,#cccccc,#dfdfdf' => t('Blue Lagoon (default)'),
    '#ffeb38,#fff385,#ffd60a,#ffe561,#8f8f8f,#e6e6e6' => t('Yellow'),
    '#ad0002,#ff5f61,#ff1a1f,#f3b3b4,#8f8f8f,#e6e6e6' => t('Red'),
    '#001efa,#bbc3fb,#1022c1,#2e41ee,#8f8f8f,#e6e6e6' => t('Blue'),
    '#015700,#7cf47c,#12c610,#c8f9c7,#8f8f8f,#e6e6e6' => t('Green'),
    '' => t('Custom'),
  );

  // See if we're using a predefined scheme.
  $palette = explode(',', array_search(t('Blue Lagoon (default)'), $options));
  $default_palette['on1'] = $palette[0];
  $default_palette['on2'] = $palette[1];
  $default_palette['hover1'] = $palette[2];
  $default_palette['hover2'] = $palette[3];
  $default_palette['off1'] = $palette[4];
  $default_palette['off2'] = $palette[5];
  $default_palette['matte'] = '#ffffff';
  $palette = variable_get('fivestar_colors', $default_palette);
  $scheme_default = implode(',', array_slice($palette, 0, count($palette) - 1));
  $form['fivestar_color_type'] = array(
    '#type' => 'select',
    '#title' => t('Color display'),
    '#options' => array(
      'default' => t('Default display'),
      'solid' => t('Solid color'),
      'gradient' => t('Gradient'),
    ),
    '#default_value' => variable_get('fivestar_color_type', 'solid'),
    '#access' => !$disabled,
  );

  // Add scheme selector.
  $form['scheme'] = array(
    '#type' => 'select',
    '#title' => t('Color set'),
    '#options' => $options,
    '#default_value' => isset($options[$scheme_default]) ? $scheme_default : '',
    '#access' => !$disabled,
  );

  // Add palette fields.
  $names = array(
    'on1' => t('On colors'),
    'on2' => NULL,
    'hover1' => t('Hover colors'),
    'hover2' => NULL,
    'off1' => t('Off colors'),
    'off2' => NULL,
    'matte' => t('Matte color'),
  );
  $form['fivestar_colors'] = array(
    '#tree' => TRUE,
    '#access' => !$disabled,
  );
  foreach ($names as $key => $name) {
    $form['fivestar_colors'][$key] = array(
      '#type' => 'textfield',
      '#title' => $name,
      '#default_value' => $palette[$key],
      '#size' => 8,
    );
  }
  if ($disabled) {
    $form['fivestar_color_type']['#value'] = 'default';
  }
  return $form;
}

/**
 * Theme color form.
 */
function theme_fivestar_color_form($form) {
  if (isset($form['#access']) && $form['#access'] == FALSE) {
    return '';
  }

  // Add Farbtastic color picker.
  drupal_add_css('misc/farbtastic/farbtastic.css', 'module', 'all', FALSE);
  drupal_add_js(drupal_get_path('module', 'fivestar') . '/js/fivestar-color.js');
  drupal_add_js('misc/farbtastic/farbtastic.js');

  // Add custom CSS/JS.
  $default_colors = array();
  foreach (element_children($form['fivestar_colors']) as $key) {
    $default_colors[$key] = $form['fivestar_colors'][$key]['#value'];
  }
  drupal_add_js(array(
    'fivestar' => array(
      'reference' => $default_colors,
      'transparent' => t('none'),
      'colorPreview' => url('fivestar/preview/color'),
    ),
  ), 'setting');
  $output = '';

  // Wrapper.
  $output .= '<div class="color-form clear-block">';

  // Color schemes.
  $output .= drupal_render($form['fivestar_color_type']);
  $output .= drupal_render($form['scheme']);

  // Palette.
  $output .= '<div id="fivestar-palette" class="clear-block">';
  foreach (element_children($form['fivestar_colors']) as $key => $name) {

    // Render pairs on a single line inside a new form element.
    if (strpos($name, '1')) {
      $name2 = str_replace('1', '2', $name);
      $title = $form['fivestar_colors'][$name]['#title'];
      unset($form['fivestar_colors'][$name]['#title']);
      $element = array(
        '#type' => 'element',
        '#title' => $title,
        '#children' => drupal_render($form['fivestar_colors'][$name]) . drupal_render($form['fivestar_colors'][$name2]),
      );
      $output .= theme('form_element', $element, $element['#children']);
    }
    $output .= drupal_render($form['fivestar_colors'][$name]);
  }
  $output .= '</div>';

  // Render the form.
  $output .= drupal_render($form);

  // Close wrapper.
  $output .= '</div>';
  return $output;
}

/**
 * Validate handler for color form.
 */
function fivestar_color_form_validate($form, &$form_state) {
  if ($form_state['values']['fivestar_color_type'] == 'default' || $_POST['fivestar_color_type'] == '') {
    $form_state['values']['fivestar_color_type'] = 'default';
    return;
  }
  foreach ($form_state['values']['fivestar_colors'] as $key => $value) {
    $form_state['values']['fivestar_colors'][$key] = trim(drupal_strtolower($value));
    if (!preg_match('/^#([0-9a-f]{3}|[0-9a-f]{6})$/', $form_state['values']['fivestar_colors'][$key]) && !($key == 'matte' && ($value == 'none' || $value == ''))) {
      form_set_error('fivestar_colors][' . $key, t('The entered value %color is not a valid hex color.', array(
        '%color' => $value,
      )));
    }
  }
}

/**
 * Submit handler for color change form.
 */
function fivestar_color_form_submit($form, &$form_state) {
  if ($form_state['values']['fivestar_colors']['matte'] == t('none')) {
    $form_state['values']['fivestar_colors']['matte'] = '';
  }

  // Check if we're using a color-enabled set of stars.
  if (!array_key_exists($form_state['values']['fivestar_widget'], $form['widget']['fivestar_color_widget']['#options']) || $form_state['values']['fivestar_color_type'] == 'default') {
    return;
  }
  $widget = str_replace('.css', '', basename($form_state['values']['fivestar_widget']));
  $widget_css = $form_state['values']['fivestar_widget'];
  $widget_rtl_css = str_replace('.css', '-rtl.css', $widget_css);
  $upload_directory = file_directory_path() . '/fivestar';
  $widget_directory = $upload_directory . '/' . $widget;
  $current_widget = variable_get('fivestar_widget', 'default');

  // Delete the previous set of stars if any from the files directory.
  if (strpos($current_widget, $upload_directory) === 0) {
    $current_widget_directory = dirname($current_widget);
    file_scan_directory($current_widget_directory, '.*', array(
      '.',
      '..',
      'CVS',
    ), 'unlink');
    rmdir($current_widget_directory);
  }

  // Check the destination directory.
  file_check_directory($upload_directory, FILE_CREATE_DIRECTORY);
  file_check_directory($widget_directory, FILE_CREATE_DIRECTORY);

  // Create the new stars.
  $star = _fivestar_color_render(str_replace($widget . '.css', 'star-template.png', $form_state['values']['fivestar_widget']), $form_state['values']['fivestar_colors'], $form_state['values']['fivestar_color_type']);
  $cancel = _fivestar_color_render(str_replace($widget . '.css', 'cancel-template.png', $form_state['values']['fivestar_widget']), $form_state['values']['fivestar_colors'], $form_state['values']['fivestar_color_type']);
  imagepng($star, $widget_directory . '/star.png');
  imagepng($cancel, $widget_directory . '/cancel.png');

  // Copy over the stylesheet.
  file_copy($widget_css, $widget_directory . '/' . $widget . '.css', FILE_EXISTS_REPLACE);

  // Copy over RTL stylesheet.
  if (file_exists($widget_rtl_css)) {
    file_copy($widget_rtl_css, $widget_directory . '/' . $widget . '-rtl.css', FILE_EXISTS_REPLACE);
  }
  $form_state['values']['fivestar_widget'] = $widget_css;
  variable_set('fivestar_colors', $form_state['values']['fivestar_colors']);
  variable_set('fivestar_color_type', $form_state['values']['fivestar_color_type']);
  drupal_clear_css_cache();
  drupal_set_message(t('Custom %name stars generated. You may need to clear your browser cache before the new stars are visible.', array(
    '%name' => t($widget),
  )));
}

/**
 * Render images that match a given palette.
 *
 * @param $source
 *   The original image source (star-template.png or cancel-template.png).
 * @param $palette
 *   The colors to be used in the generation of this image.
 * @param $type
 *   The type of color to be rendered: solid or gradient.
 */
function _fivestar_color_render($source, $palette, $type) {

  // Pull in the template image.
  $template = imagecreatefrompng($source);
  imagealphablending($template, TRUE);
  $width = imagesx($template) / 2;
  $height = imagesy($template);

  // Create a true color mask image from the left side of the template.
  $mask = imagecreatetruecolor($width, $height);
  $transparent = imagecolorallocatealpha($mask, 0, 0, 0, 127);
  imagefill($mask, 0, 0, $transparent);
  imagecopy($mask, $template, 0, 0, 0, 0, $width, $height);

  // Create a true color overlay image from the right side of the template.
  $overlay = imagecreatetruecolor($width, $height);
  $transparent = imagecolorallocatealpha($overlay, 0, 0, 0, 127);
  imagefill($overlay, 0, 0, $transparent);
  imagecopy($overlay, $template, 0, 0, $width, 0, $width, $height);

  // No need for the template image any more.
  imagedestroy($template);

  // Apply the selected colors to the mask.
  $slices = basename($source) == 'star-template.png' ? 3 : 2;
  $slice_height = floor($height / $slices);
  foreach ($slices == 2 ? array(
    'off',
    'hover',
  ) : array(
    'off',
    'on',
    'hover',
  ) as $slice => $key) {
    $slice_y = $slice_height * $slice;
    if ($type == 'gradient') {
      _fivestar_color_mask_linear_gradient($mask, $palette[$key . '1'], $palette[$key . '2'], $palette['matte'], 0, $slice_y, $width, $slice_height);
    }
    else {
      _fivestar_color_mask($mask, $palette[$key . '1'], $palette['matte'], 0, $slice_y, $width, $slice_height);
    }
  }

  // Apply the overlay on top of the mask.
  imagealphablending($mask, TRUE);
  imagecopy($mask, $overlay, 0, 0, 0, 0, $width, $height);
  imagedestroy($overlay);

  // Set the background color.
  if ($palette['matte'] == 'transparent') {

    // A simple case, just make this save as a 24-bit PNG.
    imagesavealpha($mask, TRUE);
    $return = $mask;
  }
  else {

    // If there is a matte, create a new 8-bit image, fill with the matte,
    // apply the star over the top, then set the matte to transparent.
    $return = imagecreatetruecolor($width, $height);
    $matte_rgb = _fivestar_color_unpack($palette['matte']);
    $transparent = imagecolorallocate($return, $matte_rgb[0], $matte_rgb[1], $matte_rgb[2]);
    imagefill($return, 0, 0, $transparent);
    imagealphablending($return, TRUE);
    imagecopy($return, $mask, 0, 0, 0, 0, $width, $height);
    imagecolortransparent($return, $transparent);
    imagetruecolortopalette($return, TRUE, 255);
  }
  return $return;
}

/**
 * Apply a color to a portion of a black and white mask image.
 *
 * @param $mask
 *   A GD image reference containing the mask image.
 * @param $color
 *   A hex color value (i.e. ffdd00) to apply to the mask.
 */
function _fivestar_color_mask(&$mask, $color, $matte_color, $x_offset, $y_offset, $width, $height) {
  $rgb = _fivestar_color_unpack($color);
  for ($x = $x_offset; $x < $x_offset + $width; $x++) {
    for ($y = $y_offset; $y < $y_offset + $height; $y++) {
      $current_pixel = imagecolorsforindex($mask, imagecolorat($mask, $x, $y));
      $new_color = imagecolorallocatealpha($mask, $rgb[0], $rgb[1], $rgb[2], $current_pixel['alpha']);

      // Matte coloring:
      if ($matte_color != 'transparent' && $current_pixel['alpha'] != 127) {
        $matte_rgb = _fivestar_color_unpack($matte_color);
        $matte = imagecolorallocate($mask, $matte_rgb[0], $matte_rgb[1], $matte_rgb[2]);
        imagealphablending($mask, FALSE);
        imagesetpixel($mask, $x, $y, $matte);
        imagealphablending($mask, TRUE);
        imagesetpixel($mask, $x, $y, $new_color);
      }
      else {
        imagealphablending($mask, FALSE);
        imagesetpixel($mask, $x, $y, $new_color);
        imagealphablending($mask, TRUE);
      }
    }
  }
}

/**
 * Apply a gradient to a portion of a black and white mask image.
 *
 * The two colors passed in will form a linear gradient from top to bottom.
 *
 * @param $mask
 *   A GD image reference containing the mask image.
 * @param $color1
 *   The color used at the top of the linear gradient.
 * @param $color2
 *   The color used at the bottom of the linear gradient.
 */
function _fivestar_color_mask_linear_gradient(&$mask, $color1, $color2, $matte_color, $x_offset, $y_offset, $width, $height) {
  for ($y = $y_offset; $y < $y_offset + $height; $y++) {
    $color = _fivestar_color_blend($color1, $color2, ($y - $y_offset) / $height);
    _fivestar_color_mask($mask, $color, $matte_color, 0, $y, $width, 1);
  }
}

/**
 * Blend two hex colors and return the resulting hex color.
 */
function _fivestar_color_blend($hex1, $hex2, $alpha) {
  $in1 = _fivestar_color_unpack($hex1);
  $in2 = _fivestar_color_unpack($hex2);
  for ($i = 0; $i < 3; ++$i) {
    $out[] = $in1[$i] + ($in2[$i] - $in1[$i]) * $alpha;
  }
  return _fivestar_color_pack($out);
}

/**
 * Convert a hex color into an RGB triplet.
 */
function _fivestar_color_unpack($hex, $normalize = FALSE) {
  if (strlen($hex) == 4) {
    $hex = $hex[1] . $hex[1] . $hex[2] . $hex[2] . $hex[3] . $hex[3];
  }
  $c = hexdec($hex);
  for ($i = 16; $i >= 0; $i -= 8) {
    $out[] = ($c >> $i & 0xff) / ($normalize ? 255 : 1);
  }
  return $out;
}

/**
 * Convert an RGB triplet to a hex color.
 */
function _fivestar_color_pack($rgb, $normalize = FALSE) {
  $out = 0;
  foreach ($rgb as $k => $v) {
    $out |= $v * ($normalize ? 255 : 1) << 16 - $k * 8;
  }
  return '#' . str_pad(dechex($out), 6, 0, STR_PAD_LEFT);
}

Functions

Namesort descending Description
fivestar_color_form Form callback. Returns the configuration form.
fivestar_color_form_submit Submit handler for color change form.
fivestar_color_form_validate Validate handler for color form.
fivestar_preview_color Menu callback function for fivestar/preview/color.
theme_fivestar_color_form Theme color form.
_fivestar_color_blend Blend two hex colors and return the resulting hex color.
_fivestar_color_mask Apply a color to a portion of a black and white mask image.
_fivestar_color_mask_linear_gradient Apply a gradient to a portion of a black and white mask image.
_fivestar_color_pack Convert an RGB triplet to a hex color.
_fivestar_color_render Render images that match a given palette.
_fivestar_color_unpack Convert a hex color into an RGB triplet.