View source
<?php
if (!function_exists('imagecache_actions_calculate_relative_position')) {
module_load_include('inc', 'imagecache_actions', 'utility');
}
module_load_include('inc', 'imagecache_actions', 'utility-color');
include_once dirname(__FILE__) . '/transparency.inc';
function imagecache_coloractions_image_effect_info() {
$effects = array();
$effects['coloractions_colorshift'] = array(
'label' => 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['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',
);
$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',
'summary theme' => 'coloractions_invert_summary',
);
$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',
);
return $effects;
}
function imagecache_coloractions_theme() {
return array(
'coloractions_colorshift_summary' => array(
'variables' => array(
'data' => NULL,
),
),
'coloractions_coloroverlay_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,
),
),
);
}
function imagecache_coloractions_image_style_flush($style) {
if (!is_array($style)) {
return;
}
cache_clear_all('image_styles', 'cache');
cache_clear_all('image_effects:', 'cache', TRUE);
drupal_static_reset('image_styles');
drupal_static_reset('image_effects');
$new_style = image_style_load(isset($style['name']) ? $style['name'] : NULL, isset($style['isid']) ? $style['isid'] : NULL);
if (is_array($new_style)) {
include_once dirname(__FILE__) . '/imagecache_coloractions.htaccess_creator.inc';
imagecache_coloractions_create_htaccess_for_style($new_style);
}
}
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("<p>\n Note that colorshift is a mathematical filter that doesn't always\n have the expected result.\n To shift an image precisely TO a target color,\n desaturate (greyscale) it before colorizing.\n The hue (color wheel) is the <em>direction</em> the\n existing colors are shifted. The tone (inner box) is the amount.\n Keep the tone half-way up the left site of the color box\n for best results.\n </p>"),
);
return $form;
}
function theme_coloractions_colorshift_summary(array $variables) {
return theme_imagecacheactions_rgb($variables['data']);
}
function coloractions_colorshift_effect(stdClass $image, array $data) {
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,
));
}
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']);
}
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;
}
function coloractions_coloroverlay_form(array $data) {
$defaults = array(
'RGB' => array(
'HEX' => '#E2DB6A',
),
);
$data = array_merge($defaults, (array) $data);
$form = array(
'#theme' => 'imagecache_rgb_form',
);
$form['RGB'] = imagecache_rgb_form($data['RGB']);
$form['note'] = array(
'#value' => t("<p>\n Note that color overlay is a mathematical filter that doesn't always\n have the expected result.\n To shift an image precisely TO a target color,\n desaturate (greyscale) it before colorizing.\n The hue (color wheel) is the <em>direction</em> the\n existing colors are shifted. The tone (inner box) is the amount.\n Keep the tone half-way up the left site of the color box\n for best results.\n </p>"),
);
return $form;
}
function theme_coloractions_coloroverlay_summary(array $variables) {
return theme_imagecacheactions_rgb($variables['data']);
}
function coloractions_coloroverlay_effect(stdClass $image, array $data) {
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,
));
}
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;
}
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;
}
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;
}
function theme_coloractions_brightness_summary(array $variables) {
return t("Adjust") . " : " . $variables['data']['filter_arg1'];
}
function coloractions_brightness_effect(stdClass $image, array $data) {
return image_toolkit_invoke('brightness', $image, array(
$data,
));
}
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']);
}
function image_imagemagick_brightness(stdClass $image, array $data) {
$image->ops[] = "-modulate " . (int) (100 + $data['filter_arg1'] / 128 * 100);
return TRUE;
}
function coloractions_invert_form() {
$form = array();
return $form;
}
function coloractions_invert_effect(stdClass $image, array $data) {
return image_toolkit_invoke('invert', $image, array(
$data,
));
}
function image_gd_invert(stdClass $image) {
if (!function_exists('imagefilter')) {
module_load_include('inc', 'imagecache_actions', 'imagefilter');
}
return imagefilter($image->resource, 0);
}
function image_imagemagick_invert() {
return FALSE;
}
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 <em>not</em> 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;
}
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']];
}
}
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;
}
function image_gd_convert() {
return TRUE;
}
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;
}
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']);
}
}
function coloractions_file_formats() {
return array(
'image/jpeg' => 'jpg',
'image/gif' => 'gif',
'image/png' => 'png',
);
}
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;
}
function theme_coloractions_posterize_summary(array $variables) {
return t(': Reduce to @colors color levels per channel', array(
'@colors' => $variables['data']['colors'],
));
}
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;
}
function image_gd_posterize(stdClass $image, $colors) {
$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);
$a = $rgb & $alpha_bit_mask;
$r = $rgb >> 16 & 255;
$g = $rgb >> 8 & 255;
$b = $rgb & 255;
$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);
$color_combined = $a | $new_r << 16 | $new_g << 8 | $new_b;
imagesetpixel($image->resource, $x, $y, $color_combined);
}
}
return TRUE;
}
function image_imagemagick_posterize(stdClass $image, $colors) {
$image->ops[] = ' +dither -posterize ' . (int) $colors;
return TRUE;
}
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("<p>Adjusting color levels scales the given channels to a range specified by the 'low' and 'high' values.\n These 'low' and 'high' values can be any value between 0 and 1.\n E.g. assume that 'low' is 0.2 and 'high' is 0.9.\n 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.</p>\n <p>Note that color level adjustment is a mathematical filter and a such doesn't do automatic balancing.</p>"),
),
'#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;
}
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;
}
function coloractions_validate_scale_0_1($element) {
$value = $element['#value'];
if ($value != '' && (!is_numeric($value) || (double) $value > 1.0 || (double) $value < 0.0)) {
form_error($element, t('%name must be a value between 0 and 1.', array(
'%name' => $element['#title'],
)));
}
}
function coloractions_validate_form($element) {
$independent_colors = !empty($element['independent_colors']['#value']);
if (!$independent_colors) {
coloractions_validate_low_and_high($element, 'all_colors', '');
}
else {
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 ((double) $element[$fieldset]["low{$suffix}"]['#value'] > (double) $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'],
)));
}
}
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']}]",
));
}
}
function coloractions_adjustlevels_effect(stdClass $image, array $data) {
return image_toolkit_invoke('adjustlevels', $image, array(
$data,
));
}
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;
}
function theme_coloractions_desaturatealpha_summary() {
return t(': Desaturates the image while retaining transparency.');
}
function coloractions_desaturatealpha_effect(stdClass $image, array $data) {
return image_toolkit_invoke('desaturatealpha', $image, array(
$data,
));
}
function image_gd_desaturatealpha(stdClass $image) {
imagealphablending($image->resource, FALSE);
$result = imagefilter($image->resource, IMG_FILTER_GRAYSCALE);
imagealphablending($image->resource, TRUE);
return $result;
}