t('Color Shift'), 'help' => t('Adjust image colors.'), 'effect callback' => 'coloractions_colorshift_effect', 'dimensions passthrough' => TRUE, 'form callback' => 'coloractions_colorshift_form', 'summary theme' => 'coloractions_colorshift_summary', ); $effects['imagecache_coloroverlay'] = array( 'label' => t('Color Overlay'), 'help' => t('Apply a color tint to an image (retaining blacks and whites).'), 'effect callback' => 'coloractions_coloroverlay_effect', 'dimensions passthrough' => TRUE, 'form callback' => 'coloractions_coloroverlay_form', 'summary theme' => 'coloractions_coloroverlay_summary', ); $effects['imagecache_colormultiply'] = array( 'label' => t('Color Multiply'), 'help' => t('Apply a multiply blend effect to an image. The result color is always a darker color.'), 'effect callback' => 'coloractions_colormultiply_effect', 'dimensions passthrough' => TRUE, 'form callback' => 'coloractions_colormultiply_form', 'summary theme' => 'coloractions_colormultiply_summary', ); $effects['coloractions_brightness'] = array( 'label' => t('Brightness'), 'help' => t('Adjust image brightness.'), 'effect callback' => 'coloractions_brightness_effect', 'dimensions passthrough' => TRUE, 'form callback' => 'coloractions_brightness_form', 'summary theme' => 'coloractions_brightness_summary', ); // @todo: changing inverse to invert at this place requires a hook_update_n(). $effects['coloractions_inverse'] = array( 'label' => t('Negative Image'), 'help' => t('Invert colors and brightness.'), 'effect callback' => 'coloractions_invert_effect', 'dimensions passthrough' => TRUE, 'form callback' => 'coloractions_invert_form', ); // @todo Convert may need a little more work. $effects['coloractions_convert'] = array( 'label' => t('Change file format'), 'help' => t('Choose to save the image as a different filetype.'), 'effect callback' => 'coloractions_convert_effect', 'dimensions passthrough' => TRUE, 'form callback' => 'coloractions_convert_form', 'summary theme' => 'coloractions_convert_summary', ); $effects['coloractions_posterize'] = array( 'label' => t('Posterize'), 'help' => t('Reduce the image to a limited number of color levels per channel.'), 'effect callback' => 'coloractions_posterize_effect', 'dimensions passthrough' => TRUE, 'form callback' => 'coloractions_posterize_form', 'summary theme' => 'coloractions_posterize_summary', ); $effects['imagecache_alpha'] = array( 'label' => t('Alpha Transparency'), 'help' => t('Adjust transparency.'), 'effect callback' => 'coloractions_alpha_effect', 'dimensions passthrough' => TRUE, 'form callback' => 'coloractions_alpha_form', 'summary theme' => 'coloractions_alpha_summary', ); $effects['imagecache_adjustlevels'] = array( 'label' => t('Adjust Levels'), 'help' => t('Adjust the color levels of the image.'), 'effect callback' => 'coloractions_adjustlevels_effect', 'dimensions passthrough' => TRUE, 'form callback' => 'coloractions_adjustlevels_form', 'summary theme' => 'coloractions_adjustlevels_summary', ); $effects['imagecache_desaturatealpha'] = array( 'label' => t('Desaturate Alpha'), 'help' => t('Desaturate the image while retaining transparency.'), 'effect callback' => 'coloractions_desaturatealpha_effect', 'dimensions passthrough' => TRUE, 'summary theme' => 'coloractions_desaturatealpha_summary', ); $effects['imagecache_removeanimation'] = array( 'label' => t('Remove animation'), 'help' => t('Freezes an animated image, keeping only the first frame.'), 'effect callback' => 'coloractions_removeanimation_effect', 'dimensions passthrough' => TRUE, 'form callback' => 'coloractions_removeanimation_form', 'summary theme' => 'coloractions_removeanimation_summary', ); return $effects; } /** * Implements hook_theme(). * * Registers theme functions for the effect summaries. */ function imagecache_coloractions_theme() { return array( 'coloractions_colorshift_summary' => array( 'variables' => array('data' => NULL), ), 'coloractions_coloroverlay_summary' => array( 'variables' => array('data' => NULL), ), 'coloractions_colormultiply_summary' => array( 'variables' => array('data' => NULL), ), 'coloractions_brightness_summary' => array( 'variables' => array('data' => NULL), ), 'coloractions_convert_summary' => array( 'variables' => array('data' => NULL), ), 'coloractions_posterize_summary' => array( 'variables' => array('data' => NULL), ), 'coloractions_alpha_summary' => array( 'variables' => array('data' => NULL), 'file' => 'transparency.inc', ), 'coloractions_adjustlevels_summary' => array( 'variables' => array('data' => NULL), ), 'coloractions_desaturatealpha_summary' => array( 'variables' => array('data' => NULL), ), 'coloractions_removeanimation_summary' => array( 'variables' => array('data' => NULL), ), ); } /** * Implements hook_image_style_flush(). * * This hook checks if the style contains a change format image effect and, if * so, creates an .htaccess file in the root of the derivative folder that * forces the correct Content-Type header on images served from that folder. * * @param array $style */ function imagecache_coloractions_image_style_flush($style) { if (!is_array($style)) { // See [#2190759]. return; } // Error in core: the old style + set of effects is passed in. This means // that when a convert effect is added or deleted we won't notice. So we // "change" the order of execution by duplicating these lines from // image_style_flush(): // Clear image style and effect caches. cache_clear_all('image_styles', 'cache'); cache_clear_all('image_effects:', 'cache', TRUE); drupal_static_reset('image_styles'); drupal_static_reset('image_effects'); // Now load the current state of our style. $new_style = image_style_load(isset($style['name']) ? $style['name'] : NULL, isset($style['isid']) ? $style['isid'] : NULL); // If the style is flushed because it is being deleted it might be gone. if (is_array($new_style)) { // Now back to our actual work: determine if we have to crate an .htaccess // file. include_once dirname(__FILE__) . '/imagecache_coloractions.htaccess_creator.inc'; imagecache_coloractions_create_htaccess_for_style($new_style); } } /** * Image effect form callback for the color shift effect. * * @param array $data * The current configuration for this image effect. * * @return array * The form definition for this effect. */ function coloractions_colorshift_form(array $data) { $defaults = array( 'RGB' => array( 'HEX' => '#FF0000', ), ); $data = array_merge($defaults, (array) $data); $form = array('#theme' => 'imagecache_rgb_form'); $form['RGB'] = imagecache_rgb_form($data['RGB']); $form['note'] = array('#value' => t("

Note that colorshift is a mathematical filter that doesn't always have the expected result. To shift an image precisely TO a target color, desaturate (greyscale) it before colorizing. The hue (color wheel) is the direction the existing colors are shifted. The tone (inner box) is the amount. Keep the tone half-way up the left side of the color box for best results.

")); return $form; } /** * Implements theme_hook() for the color shift effect summary. * * @param array $variables * An associative array containing: * - data: The current configuration for this image effect. * * @return string * The HTML for the summary of this image effect. * @ingroup themeable */ function theme_coloractions_colorshift_summary(array $variables) { return theme_imagecacheactions_rgb($variables['data']); } /** * Image effect callback for the color shift effect. * * @param stdClass $image * @param array $data * * @return bool * true on success, false otherwise. */ function coloractions_colorshift_effect(stdClass $image, array $data) { // convert color from hex (as it is stored in the UI) if ($data['RGB']['HEX'] && $deduced = imagecache_actions_hex2rgba($data['RGB']['HEX'])) { $data['RGB'] = array_merge($data['RGB'], $deduced); } return image_toolkit_invoke('colorshift', $image, array($data)); } /** * GD toolkit specific implementation of the color shift effect. * * @param stdClass $image * @param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_gd_colorshift(stdClass $image, array $data) { $RGB = $data['RGB']; if (!function_exists('imagefilter')) { module_load_include('inc', 'imagecache_actions', 'imagefilter'); } return imagefilter($image->resource, 4, $RGB['red'], $RGB['green'], $RGB['blue']); } /** * Imagemagick toolkit specific implementation of the color shift effect. * * @param stdClass $image * @param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_imagemagick_colorshift(stdClass $image, array $data) { $RGB = $data['RGB']; $image->ops[] = "-fill rgb" . escapeshellcmd('(') . "{$RGB['red']},{$RGB['green']},{$RGB['blue']}" . escapeshellcmd(')') . " -colorize 50" . escapeshellcmd('%'); return TRUE; } /** * Image effect form callback for the color overlay effect. * * @param array $data * The current configuration for this image effect. * * @return array * The form definition for this effect. */ function coloractions_coloroverlay_form(array $data) { $defaults = array( 'RGB' => array( 'HEX' => '', ), ); $data = array_merge($defaults, (array) $data); $form = array('#theme' => 'imagecache_rgb_form'); $form['RGB'] = imagecache_rgb_form($data['RGB']); $form['note'] = array('#value' => t("

Note that color overlay is a mathematical filter that doesn't always have the expected result. To shift an image precisely TO a target color, desaturate (greyscale) it before colorizing. The hue (color wheel) is the direction the existing colors are shifted. The tone (inner box) is the amount. Keep the tone half-way up the left side of the color box for best results.

")); return $form; } /** * Implements theme_hook() for the color overlay effect summary. * * @param array $variables * An associative array containing: * - data: The current configuration for this image effect. * * @return string * The HTML for the summary of this image effect. * @ingroup themeable */ function theme_coloractions_coloroverlay_summary(array $variables) { return theme_imagecacheactions_rgb($variables['data']); } /** * Image effect callback for the color overlay effect. * * @param stdClass $image * @param array $data * * @return bool * true on success, false otherwise. */ function coloractions_coloroverlay_effect(stdClass $image, array $data) { // convert color from hex (as it is stored in the UI) if ($data['RGB']['HEX'] && $deduced = imagecache_actions_hex2rgba($data['RGB']['HEX'])) { $data['RGB'] = array_merge($data['RGB'], $deduced); } return image_toolkit_invoke('coloroverlay', $image, array($data)); } /** * GD toolkit specific implementation of the color overlay effect. * * @param stdClass $image * @param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_gd_coloroverlay(stdClass $image, array $data) { $RGB = $data['RGB']; $w = $image->info['width']; $h = $image->info['height']; for($y=0;$y<$h;$y++) { for($x=0;$x<$w;$x++) { $rgb = imagecolorat($image->resource, $x, $y); $source = imagecolorsforindex($image->resource, $rgb); if($source['red'] <= 128){ $final_r = (2 * $source['red'] * $RGB['red'])/256; }else{ $final_r = 255 - (((255 - (2 * ($source['red'] - 128))) * (255 - $RGB['red']))/256); } if($source['green'] <= 128){ $final_g = (2 * $source['green'] * $RGB['green'])/256; }else{ $final_g = 255 - (((255 - (2 * ($source['green'] - 128))) * (255 - $RGB['green']))/256); } if($source['blue'] <= 128){ $final_b = (2 * $source['blue'] * $RGB['blue'])/256; }else{ $final_b = 255 - (((255 - (2 * ($source['blue'] - 128))) * (255 - $RGB['blue']))/256); } $final_colour = imagecolorallocatealpha($image->resource, $final_r, $final_g, $final_b, $source['alpha']); imagesetpixel($image->resource, $x, $y, $final_colour); } } return TRUE; } /** * Imagemagick toolkit specific implementation of the color overlay effect. * * @param stdClass $image * @param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_imagemagick_coloroverlay(stdClass $image, array $data) { $RGB = $data['RGB']; $image->ops[] = escapeshellcmd('(') . " +clone +matte -fill rgb" . escapeshellcmd('(') . "{$RGB['red']},{$RGB['green']},{$RGB['blue']}" . escapeshellcmd(')') . " -colorize 100" . escapeshellcmd('%') . " +clone +swap -compose overlay -composite " . escapeshellcmd(')') . " -compose SrcIn -composite"; return TRUE; } /** * Image effect form callback for the color multiply effect. * * @param array $data * The current configuration for this image effect. * * @return array * The form definition for this effect. */ function coloractions_colormultiply_form(array $data) { $defaults = array( 'RGB' => array( 'HEX' => '', ), ); $data = array_merge($defaults, (array) $data); $form = array('#theme' => 'imagecache_rgb_form'); $form['RGB'] = imagecache_rgb_form($data['RGB']); $form['note'] = array('#value' => t("

Note that color multiply is a mathematical filter that doesn't always have the expected result. To shift an image precisely TO a target color, desaturate (greyscale) it before colorizing. The hue (color wheel) is the direction the existing colors are shifted. The tone (inner box) is the amount. Keep the tone half-way up the left side of the color box for best results.

")); return $form; } /** * Implements theme_hook() for the color multiply effect summary. * * @param array $variables * An associative array containing: * - data: The current configuration for this image effect. * * @return string * The HTML for the summary of this image effect. * @ingroup themeable */ function theme_coloractions_colormultiply_summary(array $variables) { return theme_imagecacheactions_rgb($variables['data']); } /** * Image effect callback for the color multiply effect. * * @param stdClass $image * @param array $data * * @return bool * true on success, false otherwise. */ function coloractions_colormultiply_effect(stdClass $image, array $data) { // Convert color from hex (as it is stored in the UI). if ($data['RGB']['HEX'] && $deduced = imagecache_actions_hex2rgba($data['RGB']['HEX'])) { $data['RGB'] = array_merge($data['RGB'], $deduced); } return image_toolkit_invoke('colormultiply', $image, array($data)); } /** * GD toolkit specific implementation of the color multiply effect. * * @param stdClass $image * @param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_gd_colormultiply(stdClass $image, array $data) { $factor_r = $data['RGB']['red'] / 255; $factor_g = $data['RGB']['green'] / 255; $factor_b = $data['RGB']['blue'] / 255; $w = $image->info['width']; $h = $image->info['height']; for ($y = 0; $y < $h; $y++) { for ($x = 0; $x < $w; $x++) { $rgb = imagecolorat($image->resource, $x, $y); $source = imagecolorsforindex($image->resource, $rgb); $final_r = (int) ($source['red'] * $factor_r); $final_g = (int) ($source['green'] * $factor_g); $final_b = (int) ($source['blue'] * $factor_b); $final_colour = imagecolorallocate($image->resource, $final_r, $final_g, $final_b); if ($final_colour === FALSE) { return FALSE; } if (!imagesetpixel($image->resource, $x, $y, $final_colour)) { return FALSE; } } } return TRUE; } /** * Imagemagick toolkit specific implementation of the color multiply effect. * * @param stdClass $image * @param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_imagemagick_colormultiply(stdClass $image, array $data) { $multiply_color = $data['RGB']['HEX'] != '' ? '#' . ltrim($data['RGB']['HEX'], '#') : 'None'; $image->ops[] = escapeshellcmd('(') . ' +clone -fill ' . escapeshellarg($multiply_color) . ' -colorize 100 ' . escapeshellcmd(')'); $image->ops[] = '-compose multiply -composite'; return TRUE; } /** * Image effect form callback for the brightness effect. * * @param array $data * The current configuration for this image effect. * * @return array * The form definition for this effect. */ function coloractions_brightness_form(array $data) { $default = array('filter_arg1' => '100'); $data = array_merge($default, (array) $data); $form = array(); $form['help'] = array('#value' => t("The brightness effect seldom looks good on its own, but can be useful to wash out an image before making it transparent - eg for a watermark.")); $form['filter_arg1'] = array( '#type' => 'textfield', '#title' => t('Brightness'), '#description' => t('-255 - +255'), '#default_value' => $data['filter_arg1'], '#size' => 3, ); return $form; } /** * Implements theme_hook() for the brightness effect summary. * * @param array $variables * An associative array containing: * - data: The current configuration for this image effect. * * @return string * The HTML for the summary of this image effect. * @ingroup themeable */ function theme_coloractions_brightness_summary(array $variables) { return t("Adjust") . " : " . $variables['data']['filter_arg1']; } /** * Image effect callback for the brightness effect. * * @param stdClass $image * @param array $data * * @return bool * true on success, false otherwise. */ function coloractions_brightness_effect(stdClass $image, array $data) { return image_toolkit_invoke('brightness', $image, array($data)); } /** * GD toolkit specific implementation of the brightness effect. * * @param stdClass $image * @param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_gd_brightness(stdClass $image, array $data) { if (!function_exists('imagefilter')) { module_load_include('inc', 'imagecache_actions', 'imagefilter'); } return imagefilter($image->resource, 2, $data['filter_arg1']); } /** * Imagemagick toolkit specific implementation of the brightness effect. * * @param stdClass $image * @param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_imagemagick_brightness(stdClass $image, array $data) { $image->ops[] = "-modulate " . (int)(100 + ( $data['filter_arg1'] / 128 * 100 )); return TRUE; } /** * Image effect form callback for the image invert effect. * * This effect has no parameters. * * param array $data * The current configuration for this image effect. * * @return array * The form definition for this effect. */ function coloractions_invert_form(/*array $data*/) { $form = array(); return $form; } /** * Image effect callback for the image invert effect. * * @param stdClass $image * @param array $data * * @return bool * true on success, false otherwise. */ function coloractions_invert_effect(stdClass $image, array $data) { return image_toolkit_invoke('invert', $image, array($data)); } /** * GD toolkit specific implementation of the image invert effect. * * @param stdClass $image * param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_gd_invert(stdClass $image/*, array $data*/) { if (!function_exists('imagefilter')) { module_load_include('inc', 'imagecache_actions', 'imagefilter'); } return imagefilter($image->resource, 0); } /** * Imagemagick toolkit specific implementation of the image invert effect. * * @param stdClass $image * param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_imagemagick_invert(stdClass $image/*, array $data*/) { // http://www.imagemagick.org/script/command-line-options.php?#negate $image->ops[] = "-negate"; return TRUE; } /** * Image effect form callback for the convert image format effect. * * @param array $data * The current configuration for this image effect. * * @return array * The form definition for this effect. */ function coloractions_convert_form(array $data) { $defaults = array( 'format' => 'image/png', 'quality' => '75', ); $data = array_merge($defaults, $data); $form = array( 'help' => array( '#markup' => t("If you've been using transparencies in the process, the result may get saved as a PNG (as the image was treated as a one in in-between processes). If this is not desired (file sizes may get too big) you should use this process to force a flatten action before saving. "), ), 'help2' => array( '#markup' => t("For technical reasons, changing the file format within imagecache does not change the filename suffix. A png may be saved as a *.jpg or vice versa. This may confuse some browsers and image software, but most of them have no trouble. "), ), 'format' => array( '#title' => t("File format"), '#type' => 'select', '#default_value' => isset($data['format']) ? $data['format'] : 'image/png', '#options' => coloractions_file_formats(), ), 'quality' => array( '#type' => 'textfield', '#title' => t('Quality'), '#description' => t('Override the default image quality. Works for Imagemagick only. Ranges from 0 to 100. For jpg, higher values mean better image quality but bigger files. For png it is a combination of compression and filter'), '#size' => 10, '#maxlength' => 3, '#default_value' => $data['quality'], '#field_suffix' => '%', ), ); return $form; } /** * Implements theme_hook() for the convert image format effect summary. * * @param array $variables * An associative array containing: * - data: The current configuration for this image effect. * * @return string * The HTML for the summary of this image effect. * @ingroup themeable */ function theme_coloractions_convert_summary($variables) { $data = $variables['data']; $formats = coloractions_file_formats(); if ($formats[$data['format']] == 'jpg') { return t('Convert to: @format, quality: @quality%', array( '@format' => $formats[$data['format']], '@quality' => $data['quality'] )); } else { return t("Convert to") .": ". $formats[$data['format']]; } } /** * Image effect callback for the convert image format effect. * * @param stdClass $image * @param array $data * * @return bool * true on success, false otherwise. */ function coloractions_convert_effect(stdClass $image, array $data) { $formats = coloractions_file_formats(); $image->info['mime_type'] = $data['format']; $image->info['extension'] = $formats[$data['format']]; image_toolkit_invoke('convert', $image, array($data)); return TRUE; } /** * GD toolkit specific implementation of the convert image format effect. * * param stdClass $image * param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_gd_convert(/*stdClass $image, array $data*/) { return TRUE; } /** * Imagemagick toolkit specific implementation of the color shift effect. * * Converting the image format with imagemagick is done by prepending the output * format to the target file separated by a colon (:). This is done with * hook_imagemagick_arguments_alter(), see below. * * @param stdClass $image * @param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_imagemagick_convert(stdClass $image, array $data) { $image->ops['output_format'] = $image->info['extension']; $image->ops['custom_quality_value'] = (int) $data['quality']; return TRUE; } /** * Implements hook_imagemagick_arguments_alter(). * * This hook moves a change in output format from the args (action list) to the * destination format setting within the context. */ function imagecache_coloractions_imagemagick_arguments_alter(&$args, &$context) { if (isset($args['output_format'])) { $context['destination_format'] = $args['output_format']; unset($args['output_format']); } if (isset($args['custom_quality_value'])) { $args['quality'] = sprintf('-quality %d', $args['custom_quality_value']); unset($args['custom_quality_value']); } } /** * Mini mime-type list * * image_type_to_extension and image_type_to_mime_type? */ function coloractions_file_formats() { return array('image/jpeg' => 'jpg', 'image/gif' => 'gif', 'image/png' => 'png'); } /** * Image effect form callback for the posterize effect. * * @param array $data * The current configuration for this image effect. * * @return array * The form definition for this effect. */ function coloractions_posterize_form(array $data) { $form = array(); $form['colors'] = array( '#type' => 'textfield', '#title' => t('Color levels per channel'), '#default_value' => isset($data['colors']) ? $data['colors'] : '', '#required' => TRUE, '#size' => 10, '#element_validate' => array('image_effect_integer_validate'), '#allow_negative' => FALSE, '#description' => t('Number of unique values per color channel to reduce this image to. The transparency channel is left unchanged. This effect can be used to reduce file size on png images.'), ); return $form; } /** * Implements theme_hook() for the posterize effect summary. * * @param array $variables * An associative array containing: * - data: The current configuration for this image effect. * * @return string * The HTML for the summary of this image effect. * @ingroup themeable */ function theme_coloractions_posterize_summary(array $variables) { return t(': Reduce to @colors color levels per channel', array('@colors' => $variables['data']['colors'])); } /** * Image effect callback for the posterize effect. * * @param stdClass $image * @param array $data * * @return bool * true on success, false otherwise. */ function coloractions_posterize_effect(stdClass $image, array $data) { if (!image_toolkit_invoke('posterize', $image, array($data['colors']))) { watchdog('imagecache_actions', 'Image posterize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array( '%toolkit' => $image->toolkit, '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['height'] . 'x' . $image->info['height'], ), WATCHDOG_ERROR); return FALSE; } return TRUE; } /** * GD toolkit specific implementation of the posterize effect. * * Based on: * http://www.qtcentre.org/threads/36385-Posterizes-an-image-with-results-identical-to-Gimp-s-Posterize-command?p=167712#post167712 * * @param stdClass $image * @param int $colors * The parameter for this effect. * * @return bool * true on success, false otherwise. */ function image_gd_posterize(stdClass $image, $colors) { // Value step for colors per channel. $round_to = 255 / ($colors - 1); $alpha_bit_mask = 255 << 24; for ($x = imagesx($image->resource); $x--; ) { for ($y = imagesy($image->resource); $y--; ) { $rgb = imagecolorat($image->resource, $x, $y); // Use bitmasks to extract numbers we want, faster equivalent to imagecolorsforindex(). $a = $rgb & $alpha_bit_mask; // Alpha $r = $rgb >> 16 & 255; // Red $g = $rgb >> 8 & 255; // Green $b = $rgb & 255; // Blue // (int) (value + 0.5) faster equivalent to round() and already an int. $new_r = (int) (((int) ($r / $round_to + 0.5)) * $round_to + 0.5); $new_g = (int) (((int) ($g / $round_to + 0.5)) * $round_to + 0.5); $new_b = (int) (((int) ($b / $round_to + 0.5)) * $round_to + 0.5); // Faster equivalent to imagecolorallocatealpha(). $color_combined = $a | ($new_r << 16) | ($new_g << 8) | $new_b; imagesetpixel($image->resource, $x, $y, $color_combined); } } return TRUE; } /** * Imagemagick toolkit specific implementation of the color shift effect. * * @param stdClass $image * @param int $colors * The parameter for this effect. * * @return bool * true on success, false otherwise. */ function image_imagemagick_posterize(stdClass $image, $colors) { // In newer versions of ImageMagick dithering has no effect on posterize. // Turn dithering off on older versions of ImageMagick for consistency. $image->ops[] = ' +dither -posterize ' . (int) $colors; return TRUE; } /** * Image effect form callback for the brightness effect. * * Settings for color level adjustment actions. * * @param array $data * The current configuration for this image effect. * * @return array * The form definition for this effect. */ function coloractions_adjustlevels_form(array $data) { $defaults = array( 'independent_colors' => FALSE, 'all_colors' => array( 'low' => 0, 'high' => 1, ), 'per_color' => array( 'low_red' => 0, 'high_red' => 1, 'low_green' => 0, 'high_green' => 1, 'low_blue' => 0, 'high_blue' => 1, ), ); $data = array_merge($defaults, $data); $form = array( '#type' => 'container', 'help' => array( '#type' => 'markup', '#markup' => t("

Adjusting color levels scales the given channels to a range specified by the 'low' and 'high' values. These 'low' and 'high' values can be any value between 0 and 1. E.g. assume that 'low' is 0.2 and 'high' is 0.9. Pixels that had a value of 0, will get a value of 0.2 * 255 = 51 and pixels that had a value of 255, will get a value of 0.9 * 255 = 229.

Note that color level adjustment is a mathematical filter and a such doesn't do automatic balancing.

"), ), '#element_validate' => array('coloractions_validate_form'), ) ; $form['independent_colors'] = array( '#type' => 'checkbox', '#title' => t('Set each color independently'), '#default_value' => $data['independent_colors'], ); $form['all_colors'] = array( '#type' => 'container', '#tree' => TRUE, '#title' => t('All colors range'), '#required' => !$data['independent_colors'], '#states' => array( 'visible' => array(':input[name="data[independent_colors]"]' => array('checked' => FALSE)), 'required' => array(':input[name="data[independent_colors]"]' => array('checked' => FALSE)), ), ); $form['all_colors'] += coloractions_adjustlevels_form_helper(array( 'low' => array('title' => t('Low'), 'default' => $data['all_colors']['low']), 'high' => array('title' => t('High'), 'default' => $data['all_colors']['high']), )); $form['per_color'] = array( '#type' => 'container', '#tree' => TRUE, '#title' => t('Individual Color Ranges'), '#required' => $data['independent_colors'], '#states' => array( 'visible' => array(':input[name="data[independent_colors]"]' => array('checked' => TRUE)), 'required' => array(':input[name="data[independent_colors]"]' => array('checked' => TRUE)), ), ); $form['per_color'] += coloractions_adjustlevels_form_helper(array( 'low_red' => array('title' => t('Red Low'), 'default' => $data['per_color']['low_red']), 'high_red' => array('title' => t('Red High'), 'default' => $data['per_color']['high_red']), 'low_green' => array('title' => t('Green Low'), 'default' => $data['per_color']['low_green']), 'high_green' => array('title' => t('Green High'), 'default' => $data['per_color']['high_green']), 'low_blue' => array('title' => t('Blue Low'), 'default' => $data['per_color']['low_blue']), 'high_blue' => array('title' => t('Blue High'), 'default' => $data['per_color']['high_blue']), )); return $form; } /** * Helper function to create the form for the color level adjustment effect. * * @param array $data * Array containing the form elements * names as keys for array elements containing title and default value. * * @return array */ function coloractions_adjustlevels_form_helper(array $data) { $form = array(); foreach ($data as $name => $value) { $form[$name] = array( '#type' => 'textfield', '#title' => $value['title'], '#default_value' => $value['default'], '#size' => 5, '#element_validate' => array('coloractions_validate_scale_0_1'), ); } return $form; } /** * Form element validation handler for elements that should contain a number * between 0 and 1. */ function coloractions_validate_scale_0_1($element/*, &$form_state*/) { $value = $element['#value']; if ($value != '' && (!is_numeric($value) || (float) $value > 1.0 || (float) $value < 0.0)) { form_error($element, t('%name must be a value between 0 and 1.', array('%name' => $element['#title']))); } } /** * Form element validation handler that compares low and high values. */ function coloractions_validate_form($element/*, &$form_state*/) { $independent_colors = !empty($element['independent_colors']['#value']); if (!$independent_colors) { // Compare low and high. coloractions_validate_low_and_high($element, 'all_colors', ''); } else { // Compare low and high per color coloractions_validate_low_and_high($element, 'per_color', '_red'); coloractions_validate_low_and_high($element, 'per_color', '_green'); coloractions_validate_low_and_high($element, 'per_color', '_blue'); } } function coloractions_validate_low_and_high($element, $fieldset, $suffix) { if ((float) $element[$fieldset]["low$suffix"]['#value'] > (float) $element[$fieldset]["high$suffix"]['#value']) { form_error($element[$fieldset]["high$suffix"], t('%name-high must be higher then %name-low.', array('%name-high' => $element[$fieldset]["high$suffix"]['#title'], '%name-low' => $element[$fieldset]["low$suffix"]['#title']))); } } /** * Implements theme_hook() for the adjust color levels effect summary. * * @param array $variables * An associative array containing: * - data: The current configuration for this image effect. * * @return string * The HTML for the summary of this image effect. * @ingroup themeable */ function theme_coloractions_adjustlevels_summary(array $variables) { $data = $variables['data']; if (empty($data['independent_colors'])) { return t('@range', array('@range' => "[{$data['all_colors']['low']} - {$data['all_colors']['high']}]")); } else { return t('red: @red-range, green: @green-range, blue: @blue-range', array('@red-range' => "[{$data['per_color']['low_red']} - {$data['per_color']['high_red']}]", '@green-range' => "[{$data['per_color']['low_green']} - {$data['per_color']['high_green']}]", '@blue-range' => "[{$data['per_color']['low_blue']} - {$data['per_color']['high_blue']}]")); } } /** * Image effect callback for the adjust levels effect. * * @param stdClass $image * @param array $data * * @return bool * true on success, false otherwise. */ function coloractions_adjustlevels_effect(stdClass $image, array $data) { return image_toolkit_invoke('adjustlevels', $image, array($data)); } /** * GD toolkit specific implementation of the adjust levels effect. * * @param stdClass $image * @param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_gd_adjustlevels(stdClass $image, array $data) { $width = $image->info['width']; $height = $image->info['height']; if ($data['independent_colors']) { $lower_r = $data['per_color']['low_red'] * 255; $factor_r = ($data['per_color']['high_red'] * 255 - $lower_r) / 255; $lower_g = $data['per_color']['low_green'] * 255; $factor_g = ($data['per_color']['high_green'] * 255 - $lower_g) / 255; $lower_b = $data['per_color']['low_blue'] * 255; $factor_b = ($data['per_color']['high_blue'] * 255 - $lower_b) / 255; } else { $lower_r = $lower_g = $lower_b = $data['all_colors']['low'] * 255; $factor_r = $factor_g = $factor_b = ($data['all_colors']['high'] * 255 - $lower_r) / 255; } for ($y = 0; $y < $height; $y++) { for ($x = 0; $x < $width; $x++) { $rgb = imagecolorat($image->resource, $x, $y); $source = imagecolorsforindex($image->resource, $rgb); $final_r = $lower_r + $factor_r * $source['red']; $final_g = $lower_g + $factor_g * $source['green']; $final_b = $lower_b + $factor_b * $source['blue']; $final_colour = imagecolorallocatealpha($image->resource, $final_r, $final_g, $final_b, $source['alpha']); imagesetpixel($image->resource, $x, $y, $final_colour); } } return TRUE; } /** * Implements theme_hook() for the desaturate alpha effect summary. * * param array $variables * An associative array containing: * - data: The current configuration for this image effect. * * @return string * The HTML for the summary of this image effect. * @ingroup themeable */ function theme_coloractions_desaturatealpha_summary(/*array $variables*/) { return t(': Desaturates the image while retaining transparency.'); } /** * Image effect callback for the desaturate alpha effect. * * @param stdClass $image * @param array $data * * @return bool * true on success, false otherwise. */ function coloractions_desaturatealpha_effect(stdClass $image, array $data) { return image_toolkit_invoke('desaturatealpha', $image, array($data)); } /** * GD toolkit specific implementation of the adjust levels effect. * * @param stdClass $image * param array $data * The parameters for this effect. * * @return bool * true on success, false otherwise. */ function image_gd_desaturatealpha(stdClass $image/*, array $data*/) { imagealphablending($image->resource, FALSE); $result = imagefilter($image->resource, IMG_FILTER_GRAYSCALE); imagealphablending($image->resource, TRUE); return $result; } /** * Image effect form callback for the remove animation effect. * * This effect has no parameters. * * param array $data * The current configuration for this image effect. * * @return array * The form definition for this effect. */ function coloractions_removeanimation_form(/*array $data*/) { $form = array(); $form['help'] = array( '#markup' => "

There are no user-configurable options for this effect.

This image effect will remove all animation from a gif image, keeping only the first frame. Some notes:

", ); return $form; } /** * Implements theme_hook() for the remove animation effect summary. * * param array $variables * An associative array containing: * - data: The current configuration for this image effect. * * @return string * The HTML for the summary of this image effect. * @ingroup themeable */ function theme_coloractions_removeanimation_summary(/*array $variables*/) { return t('Remove animation, keeping only the first frame.'); } /** * Image effect callback for the remove animation effect. * * @param stdClass $image * @param array $data * * @return bool * True on success, false otherwise. */ function coloractions_removeanimation_effect(stdClass $image, array $data) { return image_toolkit_invoke('removeanimation', $image, array($data)); } /** * GD toolkit specific implementation of the remove animation effect. * * param stdClass $image * param array $data * The parameters for this effect. * * @return bool * Always true, GD removes animations anyway. */ function image_gd_removeanimation(/*stdClass $image, array $data*/) { return TRUE; } /** * Imagemagick toolkit specific implementation of the remove animation effect. * * @param stdClass $image * param array $data * The parameters for this effect. * * @return bool * True on success, false otherwise. */ function image_imagemagick_removeanimation(stdClass $image/*, array $data*/) { if ($image->info['mime_type'] === 'image/gif') { $image->ops[] = '-delete 1--1'; } return TRUE; }