updated i18n i10n_update workflwo imachecache_actions

This commit is contained in:
2020-06-23 12:41:21 +02:00
parent 8c39ab6f4a
commit 092758ecb0
134 changed files with 2894 additions and 1205 deletions
@@ -4,6 +4,45 @@ Imagecache Actions
Imagecache Actions 7.x-1.x-dev
------------------------------
Imagecache Actions 7.x-1.12
---------------------------
- [#3151871]: by DamienMcKenna: EXIF autorotate support for remote images.
- [#3117901] by jacob.embree: Curly brace syntax for accessing array elements and
string offsets has been deprecated in PHP 7.4.
Imagecache Actions 7.x-1.11
---------------------------
- [#3072639]: PDOException: SQLSTATE[HY000]: General error: 25 bind or column
index out of range: INSERT INTO {menu_router}.
Imagecache Actions 7.x-1.10
---------------------------
- Use json for exporting image styles.
- [#2988487] by AdamEvertsson: Minor spelling error.
Imagecache Actions 7.x-1.9
--------------------------
- [#2760121] by lebster, fietserwin: Add a "Perspective" effect.
- [#2947014]: PHP 7.2 warning that each() has been deprecated.
Imagecache Actions 7.x-1.8
--------------------------
- [#2917097]: PHP 7.1 Warning "A non-numeric value encountered".
- [#2905130] by bserem, fietserwin: Remove animation from moving images.
- [#2888678] by das-peter: ImageMagick: Add support for invert effect.
- [#1099300]: Notice: A non well formed numeric value encountered in
image_gd_definecanvas() (line 381 of canvasactions.inc).
- [#2719661] by SKAUGHT: Division by zero.
- [#2717789]: Prevent exif_read_data() warnings.
- Warning: Theme hook coloractions_invert_summary not found.
- [#2696225] by mnlund, fietserwin: Add support for multiply color blending.
- by fietserwin: image_styles_admin: sort image styles by label.
- [#2039379]: Add some better examples for using custom actions.
- Made more contextual information (image style and image effect) available in
the custom action and text image effects.
- [#2690337] by Ajithlal, fietserwin: Would like a feature for converting case
in text effect.
Imagecache Actions 7.x-1.7
--------------------------
- [#2671526]: Fatal error on image style flush when image styles are defined in
@@ -5,9 +5,8 @@ core = 7.x
dependencies[] = image
; Information added by Drupal.org packaging script on 2016-02-19
version = "7.x-1.7"
; Information added by Drupal.org packaging script on 2020-06-16
version = "7.x-1.12"
core = "7.x"
project = "imagecache_actions"
datestamp = "1455872655"
datestamp = "1592294437"
@@ -129,7 +129,32 @@ function image_gd_imagecache_autorotate(stdClass $image) {
watchdog('imagecache_actions', 'Image %file could not be auto-rotated: !message', array('%file' => $image->source, '!message' => t('The exif_read_data() function is not available in this PHP installation. You probably have to enable the exif extension.')));
return FALSE;
}
$exif = exif_read_data(drupal_realpath($image->source));
// Read and check result.
$path = drupal_realpath($image->source);
if ($path) {
$exif = @exif_read_data($path);
}
else {
// drupal_realpath and exif fail for remote file systems like S3.
// Copy to a local temporary folder and retry.
$base = drupal_basename($image->source);
$uniq = md5(microtime() . $image->source);
$path = file_directory_temp() . '/' . $uniq . '-' . $base;
file_unmanaged_copy($image->source, $path);
$exif = @exif_read_data($path);
// Cleanup so we don't leave files behind.
// The server would get them later on a reboot, but who knows when that
// would be.
file_unmanaged_delete($path);
}
if ($exif === FALSE) {
watchdog('imagecache_actions', 'Image %file could not be auto-rotated: !message', array('%file' => $image->source, '!message' => t('The exif_read_data() function returned FALSE.')));
return FALSE;
}
if (isset($exif['Orientation'])) {
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html:
// 1 = Horizontal (normal)
@@ -181,7 +206,7 @@ function image_imagemagick_imagecache_autorotate(stdClass $image) {
// image when it should not, we only pass the auto-orient argument when the
// exif extension could detect the 'Orientation' tag.
if (function_exists('exif_read_data')) {
$exif = exif_read_data(drupal_realpath($image->source));
$exif = @exif_read_data(drupal_realpath($image->source));
if (isset($exif['Orientation'])) {
switch ($exif['Orientation']) {
case 1:
@@ -204,13 +229,18 @@ function image_imagemagick_imagecache_autorotate(stdClass $image) {
break;
}
}
elseif ($exif === FALSE && $image->extension === 'jpg') {
watchdog('imagecache_actions', 'Image %file could not be auto-rotated: !message', array('%file' => $image->source, '!message' => t('The exif_read_data() function returned FALSE.')));
}
}
else {
// We do add the auto-orient argument to IM. IM will determine itself
// whether to rotate or not.
$image->ops[] = '-auto-orient';
// However we cannot keep track of the dimensions anymore.
$image->info['width'] = $image->info['height'] = NULL;
if ($image->info['width'] !== $image->info['height']) {
$image->info['width'] = $image->info['height'] = NULL;;
}
}
return TRUE;
}
@@ -269,6 +269,7 @@ function canvasactions_definecanvas_form(array $data) {
return $form;
}
/** @noinspection PhpDocMissingThrowsInspection */
/**
* Implements theme_hook() for the define canvas effect summary.
*
@@ -295,6 +296,7 @@ function theme_canvasactions_definecanvas_summary(array $variables) {
$output .= ' top:' . $data['relative']['topdiff'];
$output .= ' bottom:' . $data['relative']['bottomdiff'];
}
/** @noinspection PhpUnhandledExceptionInspection */
$output .= theme('imagecacheactions_rgb', array('RGB' => $data['RGB']));
$output .= ($data['under']) ? t(" <b>under</b> image ") : t(" <b>over</b> image ");
return $output;
@@ -320,19 +322,19 @@ function canvasactions_definecanvas_effect(stdClass $image, array $data) {
$data['exact']['height'] = $image->info['height'];
}
$targetsize['width'] = imagecache_actions_percent_filter($data['exact']['width'], $image->info['width']);
$targetsize['height'] = imagecache_actions_percent_filter($data['exact']['height'], $image->info['height']);
$target_size['width'] = imagecache_actions_percent_filter($data['exact']['width'], $image->info['width']);
$target_size['height'] = imagecache_actions_percent_filter($data['exact']['height'], $image->info['height']);
$targetsize['left'] = image_filter_keyword($data['exact']['xpos'], $targetsize['width'], $image->info['width']);
$targetsize['top'] = image_filter_keyword($data['exact']['ypos'], $targetsize['height'], $image->info['height']);
$target_size['left'] = image_filter_keyword($data['exact']['xpos'], $target_size['width'], $image->info['width']);
$target_size['top'] = image_filter_keyword($data['exact']['ypos'], $target_size['height'], $image->info['height']);
}
else {
// Calculate relative size.
$targetsize['width'] = $image->info['width'] + $data['relative']['leftdiff'] + $data['relative']['rightdiff'];
$targetsize['height'] = $image->info['height'] + $data['relative']['topdiff'] + $data['relative']['bottomdiff'];
$targetsize['left'] = $data['relative']['leftdiff'];
$targetsize['top'] = $data['relative']['topdiff'];
$target_size['width'] = $image->info['width'] + ((int) $data['relative']['leftdiff']) + ((int) $data['relative']['rightdiff']);
$target_size['height'] = $image->info['height'] + ((int) $data['relative']['topdiff']) + ((int) $data['relative']['bottomdiff']);
$target_size['left'] = (int) $data['relative']['leftdiff'];
$target_size['top'] = (int) $data['relative']['topdiff'];
}
// Convert from hex (as it is stored in the UI).
@@ -341,12 +343,12 @@ function canvasactions_definecanvas_effect(stdClass $image, array $data) {
}
// All the math is done, now defer to the toolkit in use.
$data['targetsize'] = $targetsize;
$data['targetsize'] = $target_size;
$success = image_toolkit_invoke('definecanvas', $image, array($data));
if ($success) {
$image->info['width'] = $targetsize['width'];
$image->info['height'] = $targetsize['height'];
$image->info['width'] = $target_size['width'];
$image->info['height'] = $target_size['height'];
}
return $success;
}
@@ -363,10 +365,10 @@ function canvasactions_definecanvas_effect(stdClass $image, array $data) {
* true on success, false otherwise.
*/
function image_gd_definecanvas(stdClass $image, array $data) {
$targetsize = $data['targetsize'];
$target_size = $data['targetsize'];
$RGB = $data['RGB'];
$newcanvas = imagecreatetruecolor($targetsize['width'], $targetsize['height']);
$newcanvas = imagecreatetruecolor($target_size['width'], $target_size['height']);
imagesavealpha($newcanvas, TRUE);
imagealphablending($newcanvas, FALSE);
imagesavealpha($image->resource, TRUE);
@@ -378,19 +380,19 @@ function image_gd_definecanvas(stdClass $image, array $data) {
// No color, attempt transparency, assume white.
$background = imagecolorallocatealpha($newcanvas, 255, 255, 255, 127);
}
imagefilledrectangle($newcanvas, 0, 0, $targetsize['width'], $targetsize['height'], $background);
imagefilledrectangle($newcanvas, 0, 0, $target_size['width'], $target_size['height'], $background);
if ($data['under']) {
$canvas_object = new stdClass();
$canvas_object->resource = $newcanvas;
$canvas_object->info = array(
'width' => $targetsize['width'],
'height' => $targetsize['height'],
'width' => $target_size['width'],
'height' => $target_size['height'],
'mime_type' => $image->info['mime_type'],
'extension' => $image->info['extension'],
);
$canvas_object->toolkit = $image->toolkit;
image_overlay($image, $canvas_object, $targetsize['left'], $targetsize['top'], 100, TRUE);
image_overlay($image, $canvas_object, $target_size['left'], $target_size['top'], 100, TRUE);
}
else {
$image->resource = $newcanvas;
@@ -421,10 +423,10 @@ function image_imagemagick_definecanvas(stdClass $image, $data) {
$compose_operator = $data['under'] ? 'src-over' : 'dst-over';
$image->ops[] = "-compose $compose_operator";
$targetsize = $data['targetsize'];
$geometry = sprintf('%dx%d', $targetsize['width'], $targetsize['height']);
if ($targetsize['left'] || $targetsize['top']) {
$geometry .= sprintf('%+d%+d', -$targetsize['left'], -$targetsize['top']);
$target_size = $data['targetsize'];
$geometry = sprintf('%dx%d', $target_size['width'], $target_size['height']);
if ($target_size['left'] || $target_size['top']) {
$geometry .= sprintf('%+d%+d', -$target_size['left'], -$target_size['top']);
}
$image->ops[] = '-extent ' . escapeshellarg($geometry);
@@ -594,7 +596,7 @@ function canvasactions_canvas2file_effect(stdClass $image, array $data) {
// This func modifies the underlay image by ref, placing the current canvas on it.
if (image_overlay($image, $underlay, $data['xpos'], $data['ypos'], $data['alpha'], TRUE)) {
//$image->resource = $underlay->resource;
$image = $underlay; //@todo: this is a no-op.
//$image = $underlay; //@todo: this is a no-op.
return TRUE;
}
}
@@ -935,7 +937,7 @@ function canvasactions_aspect_effect(stdClass $image, array $data) {
* An associative array containing the effect data.
*/
function canvasactions_aspect_dimensions(array &$dimensions, array $data) {
if (!isset($dimensions['width']) || !isset($dimensions['height'])) {
if (empty($dimensions['width']) || empty($dimensions['height'])) {
// We cannot know which preset would be executed and thus cannot know the
// resulting dimensions, unless both styles return the same dimensions:
$landscape_dimensions = $portrait_dimensions = $dimensions;
@@ -998,9 +1000,13 @@ function canvasactions_resizepercent_form(array $data) {
return $form;
}
/**
* Element validate handler to ensure that a positive number is specified.
*
* @param array $element
* The form element to validate.
* @param array $form_state
* The form state.
*/
function canvasactions_resizepercent_validate($element, &$form_state) {
element_validate_number($element, $form_state);
@@ -1247,3 +1253,441 @@ function image_imagemagick_interlace($image/*, array $data*/) {
$image->ops[] = '-interlace Plane';
return TRUE;
}
/**
* Image effect form callback for the Perspective effect.
*
* @param array $data
* The current configuration for this image effect.
*
* @return array
* The form definition for this effect.
*/
function canvasactions_perspective_form(array $data) {
$defaults = array(
'vanish' => 'right',
'symmetry' => 'symmetrical',
'distortion' => 14,
'opposite_distortion' => 10,
);
$data = array_merge($defaults, $data);
$form['vanish'] = array(
'#type' => 'radios',
'#title' => t('Vanishing point'),
'#options' => array(
'top' => t('Top'),
'left' => t('Left'),
'right' => t('Right'),
'bottom' => t('Bottom'),
),
'#theme' => 'canvasactions_perspective_anchor',
'#default_value' => $data['vanish'],
'#description' => t('The position of the vanishing point relative to the source image.'),
);
$form['symmetry'] = array(
'#type' => 'radios',
'#title' => t('Symmetry of image perspective'),
'#description' => t('If symmetrical, the perspective effect will be built symmetrically. If asymmetrical, you can set different distortion values for both sides. Mathematically speaking: symmetrical distortion results in an isosceles trapezoid, whereas asymmetrical distortion results in just an acute trapezoid.'),
'#default_value' => $data['symmetry'],
'#options' => array(
'symmetrical' => t('Symmetrical perspective'),
'asymmetrical' => t('Asymmetrical perspective'),
),
);
$form['distortion'] = array(
'#type' => 'textfield',
'#title' => t('Distortion'),
'#field_suffix' => '%',
'#size' => 5,
'#default_value' => $data['distortion'],
'#element_validate' => array('canvasactions_perspective_distortion_validate'),
'#description' => t('How much the corner(s) (on the vanishing point side of the image) should move to the horizon (i.e. the line containing the vanishing point). With 0% you will have no perspective effect at all and the vanishing point will be infinitely far away. With a sum of 100%, the 2 corner(s) and the vanishing point will be the same, resulting in a triangle instead of a trapezoid. For a pleasing effect, you should choose (a) number(s) between 0 and 35%, especially with ImageMagick as that toolkit also adds some stretching within the image.'),
);
$form['opposite_distortion'] = array(
'#type' => 'textfield',
'#title' => t('Distortion for opposite side'),
'#states' => array(
'visible' => array(
':input[name="data[symmetry]"]' => array('value' => 'asymmetrical'),
),
),
'#field_suffix' => '%',
'#size' => 5,
'#default_value' => $data['opposite_distortion'],
'#element_validate' => array('canvasactions_perspective_distortion_validate'),
'#description' => t('How much the 2nd corner on the vanishing point side of the image should move to the horizon line containing the vanishing point.'),
);
$form['additional_help'] = array(
'#markup' => '<p>Some notes:</p>
<ul><li>This effect adds a perspective effect to an image.
Normally, to get a realistic effect, the side that gets the perspective effect should be reduced in size.
However, this effect does not do so, as it is easy to add a (percentage) resize effect to the image style yourself.
A resize to 85% of the original size is a good start when experimenting.</li>
<li>CSS3 also defines <a href="https://www.w3.org/TR/2009/WD-css3-3d-transforms-20090320/#perspective-property">3D perspective transformations</a>.
So you might get some of the results of ths effect with pure CSS as well.</li></ul>',
);
return $form;
}
/**
* Form element validation handler for distortion.
*
* @param array $element
* The form element to validate.
* @param array $form_state
* The form state.
*/
function canvasactions_perspective_distortion_validate($element, &$form_state) {
$symmetrical = $form_state['values']['data']['symmetry'] === 'symmetrical';
$element_name = $element['#name'];
// Do not check opposite distortion if it is hidden in the UI.
if (!$symmetrical || $element_name === 'data[distortion]') {
$value = $element['#value'];
// Check value itself: a number between 0 and 100 (50 if symmetrical):
$max_value = $symmetrical ? 50 : 100;
if (!is_numeric($value) || $value < 0 || $value >= $max_value) {
if ($symmetrical) {
form_error($element, t('!name must be a value between 0 and 50.', array('!name' => $element['#title'])));
}
else {
form_error($element, t('!name must be a value between 0 and 100.',array('!name' => $element['#title'])));
}
}
// Sum of both distortion values should also be smaller then 100.
if (!$symmetrical) {
$other_value_name = $element_name === 'data[distortion]' ? 'opposite_distortion' : 'distortion';
$other_value = $form_state['values']['data'][$other_value_name];
if (is_numeric($other_value) && $value + $other_value >= 100) {
form_error($element, t('The sum of %name and %name2 must be lower then 100.',
array(
'%name' => $element['#title'],
'%name2' => $other_value_name === 'distortion' ? t('Distortion') : t('Distortion for opposite side'),
))
);
}
}
}
}
/**
* Implements theme_hook() for the define perspective 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_canvasactions_perspective_summary(array $variables) {
$data = $variables['data'];
$output = array();
$output[] = t('%symmetry. Vanishing point: %vanish.',
array(
'%symmetry' => $data['symmetry'],
'%vanish' => $data['vanish'],
));
if ($data['symmetry'] == 'asymmetrical') {
switch ($data['vanish']) {
case 'top':
case 'bottom':
$output[] = t('Left distortion: %distortion, right distortion: %opposite_distortion.',
array(
'%distortion' => $data['distortion'] . '%',
'%opposite_distortion' => $data['opposite_distortion'] . '%',
));
break;
case 'right':
case 'left':
$output[] = t('Top distortion: %distortion, bottom distortion: %opposite_distortion.',
array(
'%distortion' => $data['distortion'] . '%',
'%opposite_distortion' => $data['opposite_distortion'] . '%',
));
break;
}
}
else {
$output[] = t('Distortion: %distortion.', array('%distortion' => $data['distortion'] . '%'));
}
return implode(' ', $output);
}
/**
* Image effect callback for the Perspective effect.
*
* @param stdClass $image
* Image object containing the image to operate on.
* @param array $data
* The current configuration for this image effect, contains the keys:
* distortion, vanish, symmetry and opposite_distortion options.
*
* @return bool
* True on success, false otherwise.
*/
function canvasactions_perspective_effect(stdClass $image, array $data) {
if (!image_toolkit_invoke('perspective', $image, array($data))) {
watchdog('imagecache_canvasactions', 'Image perspective transform 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 image Perspective effect.
*
* @param stdClass $image
* Image object containing the image resource to operate on.
* @param array $data
* The current configuration for this image effect, contains the keys:
* distortion, vanish, symmetry and opposite_distortion options.
*
* @return bool
* True on success, false otherwise.
*/
function image_gd_perspective(stdClass $image, $data) {
$width = $image->info['width'];
$height = $image->info['height'];
$distortion = $data['distortion'];
$opposite_distortion = $data['symmetry'] === 'symmetrical' ? $distortion : $data['opposite_distortion'];
// To reduce distortion, we work with a temporary hires version of the image.
// @todo: during processing we have 2 resources this size: this might crash on memory limits: warn and/or prevent.
$multiplier = 3;
$hires_width = $width * $multiplier;
$hires_height = $height * $multiplier;
$hires_source_image = imagecreatetruecolor($hires_width, $hires_height);
$transparent_white = imagecolorallocatealpha($hires_source_image, 255, 255, 255, 127);
imagealphablending($hires_source_image, FALSE);
imagefilledrectangle($hires_source_image, 0, 0, $hires_width, $hires_height, $transparent_white);
imagesavealpha($hires_source_image, TRUE);
imagecopyresized($hires_source_image, $image->resource, 0, 0, 0, 0, $hires_width, $hires_height, $width, $height);
imagedestroy($image->resource);
// Creating a hires target canvas to apply the perspective effect on.
$hires_target_image = imagecreatetruecolor($hires_width, $hires_height);
$transparent_white = imagecolorallocatealpha($hires_target_image, 255, 255, 255, 127);
// We don't want to blend: the transparent background we set is only for the
// parts that do not get covered.
imagealphablending($hires_target_image, FALSE);
imagefilledrectangle($hires_target_image, 0, 0, $hires_width, $hires_height, $transparent_white);
// Building perspective effect with help four point distortion methods.
// On each step found new distortion point by right triangle formula.
switch ($data['vanish']) {
case 'top':
$left = round($hires_width * $distortion / 100);
$right = round($hires_width - ($hires_width * (100 - $opposite_distortion) / 100));
$tg_beta_left = $left / $hires_height;
$tg_beta_right = $right / $hires_height;
for ($y = 0; $y < $hires_height; $y++) {
$new_left = ($hires_height - $y) * $tg_beta_left;
$new_right = ($hires_height - $y) * $tg_beta_right;
$new_width = $hires_width - $new_left - $new_right;
imagecopyresampled($hires_target_image, $hires_source_image,
$new_left, $y,
0, $y,
$new_width, 1,
$hires_width, 1);
}
break;
case 'bottom':
$left = round($hires_width * $distortion / 100);
$right = round($hires_width - ($hires_width * (100 - $opposite_distortion) / 100));
$tg_beta_left = $left / $hires_height;
$tg_beta_right = $right / $hires_height;
for ($y = $hires_height; $y > 0; $y--) {
$new_left = $y * $tg_beta_left;
$new_right = $y * $tg_beta_right;
$new_width = $hires_width - $new_left - $new_right;
imagecopyresampled($hires_target_image, $hires_source_image,
$new_left, $y,
0, $y,
$new_width, 1,
$hires_width, 1);
}
break;
case 'right':
$top = round($hires_height * $distortion / 100);
$bottom = round($hires_height - ($hires_height * (100 - $opposite_distortion) / 100));
$tg_beta_top = $top / $hires_width;
$tg_beta_bottom = $bottom / $hires_width;
for ($x = $hires_width; $x > 0; $x--) {
$new_top = $x * $tg_beta_top;
$new_bottom = $x * $tg_beta_bottom;
$new_height = $hires_height - $new_top - $new_bottom;
imagecopyresampled($hires_target_image, $hires_source_image,
$x, $new_top,
$x, 0,
1, $new_height,
1, $hires_height);
}
break;
case 'left':
$top = round($hires_height * $distortion / 100);
$bottom = round($hires_height - ($hires_height * (100 - $opposite_distortion) / 100));
$tg_beta_top = $top / $hires_width;
$tg_beta_bottom = $bottom / $hires_width;
for ($x = 0; $x < $hires_width; $x++) {
$new_top = ($hires_width - $x) * $tg_beta_top;
$new_bottom = ($hires_width - $x) * $tg_beta_bottom;
$new_height = $hires_height - $new_top - $new_bottom;
imagecopyresampled($hires_target_image, $hires_source_image,
$x, $new_top,
$x, 0,
1, $new_height,
1, $hires_height);
}
break;
}
imagedestroy($hires_source_image);
imagealphablending($hires_target_image, FALSE);
imagesavealpha($hires_target_image, TRUE);
// Return image with perspective effect to original size.
$target_image = imagecreatetruecolor($width, $height);
imagealphablending($target_image, FALSE);
imagecopyresampled($target_image, $hires_target_image, 0, 0, 0, 0, $width, $height, $hires_width, $hires_height);
imagedestroy($hires_target_image);
imagesavealpha($target_image, TRUE);
$image->resource = $target_image;
return TRUE;
}
/**
* Imagemagick toolkit specific implementation of the image Perspective effect.
*
* @param stdClass $image
* Image object containing the image to operate on.
* @param array $data
* The current configuration for this image effect, contains the keys
* distortion, vanish, symmetry and opposite_distortion options.
*
* @return bool
* True on success, false otherwise.
*/
function image_imagemagick_perspective(stdClass $image, $data) {
$width = $image->info['width'];
$height = $image->info['height'];
$distortion = $data['distortion'];
$opposite_distortion = $data['symmetry'] === 'symmetrical' ? $distortion : $data['opposite_distortion'];
switch ($data['vanish']) {
case 'top':
$left = round($width * $distortion / 100);
$right = round($width * (100 - $opposite_distortion) / 100);
$perspective_arg = "0,0,$left,0 0,$height,0,$height $width,0,$right,0 $width,$height,$width,$height";
break;
case 'right':
default: // Prevents warning about possibly undefined variable.
$top = round($height * $distortion / 100);
$bottom = round($height * (100 - $opposite_distortion) / 100);
$perspective_arg = "0,0,0,0 0,$height,0,$height $width,0,$width,$top $width,$height,$width,$bottom";
break;
case 'bottom':
$left = round($width * $distortion / 100);
$right = round($width * (100 - $opposite_distortion) / 100);
$perspective_arg = "0,0,0,0 0,$height,$left,$height $width,0,$width,0 $width,$height,$right,$height";
break;
case 'left':
$top = round($height * $distortion / 100);
$bottom = round($height * (100 - $opposite_distortion) / 100);
$perspective_arg = "0,0,0,$top 0,$height,0,$bottom $width,0,$width,0 $width,$height,$width,$height";
break;
}
$transparent_white = escapeshellarg('#ffffffff');
$perspective = escapeshellarg($perspective_arg);
$image->ops[] = "-background $transparent_white -virtual-pixel background -distort Perspective $perspective";
return TRUE;
}
/** @noinspection PhpDocMissingThrowsInspection */
/**
* Implements theme_hook().
*
* @param array $variables
* An associative array containing:
* - element: A render element containing radio buttons.
*
* @return string
* The HTML for a 3x3 grid of checkboxes for image anchors.
* @ingroup themeable
*/
function theme_canvasactions_perspective_anchor($variables) {
$element = $variables['element'];
$rows = $row = $option = array();
$blank = array('#markup' => '');
$blank = drupal_render($blank);
/** @noinspection PhpUnhandledExceptionInspection */
$image = array(
'#markup' => theme('image', array(
'path' => drupal_get_path('module', 'image') . '/sample.png',
'attributes' => array('width' => "40", 'height' => "40"),
)),
);
$image = drupal_render($image);
foreach (element_children($element) as $key) {
$element[$key]['#attributes']['title'] = $element[$key]['#title'];
unset($element[$key]['#title']);
$option[] = drupal_render($element[$key]);
}
$row[] = $blank;
$row[] = $option[0];
$row[] = $blank;
$rows[] = $row;
$row = array();
$row[] = $option[1];
$row[] = $image;
$row[] = $option[2];
$rows[] = $row;
$row = array();
$row[] = $blank;
$row[] = $option[3];
$row[] = $blank;
$rows[] = $row;
/** @noinspection PhpUnhandledExceptionInspection */
return theme('table', array('header' => array(), 'rows' => $rows, 'attributes' => array('class' => array('image-anchor'))));
}
@@ -6,9 +6,8 @@ core = 7.x
dependencies[] = imagecache_actions
dependencies[] = image
; Information added by Drupal.org packaging script on 2016-02-19
version = "7.x-1.7"
; Information added by Drupal.org packaging script on 2020-06-16
version = "7.x-1.12"
core = "7.x"
project = "imagecache_actions"
datestamp = "1455872655"
datestamp = "1592294437"
@@ -145,6 +145,15 @@ function imagecache_canvasactions_image_effect_info() {
'form callback' => 'canvasactions_interlace_form',
);
$effects['canvasactions_perspective'] = array(
'label' => t('Perspective transform'),
'help' => t('Adds a perspective transformation to the image.'),
'effect callback' => 'canvasactions_perspective_effect',
'dimensions passthrough' => TRUE,
'form callback' => 'canvasactions_perspective_form',
'summary theme' => 'canvasactions_perspective_summary',
);
return $effects;
}
@@ -191,6 +200,13 @@ function imagecache_canvasactions_theme() {
'variables' => array('data' => NULL),
'file' => 'canvasactions.inc',
),
'canvasactions_perspective_summary' => array(
'variables' => array('data' => NULL),
'file' => 'canvasactions.inc',
),
'canvasactions_perspective_anchor' => array(
'render element' => 'element',
),
);
}
@@ -152,8 +152,8 @@ function image_gd_roundedcorners($image, $action) {
}
// key can be 'tl', 'tr', 'bl', 'br'.
$is_bottom = ($key{0} == 'b');
$is_right = ($key{1} == 'r');
$is_bottom = ($key[0] == 'b');
$is_right = ($key[1] == 'r');
// dx and dy are in "continuous coordinates",
// and mark the distance of the pixel middle to the image border.
@@ -98,7 +98,7 @@ $presets['cheap_dropshadow'] = array (
'data' =>
array (
'width' => '200',
'height' => '100%',
'height' => '',
'upscale' => 0,
),
),
@@ -10,9 +10,8 @@ files[] = imagecache_coloractions.module
files[] = transparency.inc
files[] = tests/green.imagecache_preset.inc
; Information added by Drupal.org packaging script on 2016-02-19
version = "7.x-1.7"
; Information added by Drupal.org packaging script on 2020-06-16
version = "7.x-1.12"
core = "7.x"
project = "imagecache_actions"
datestamp = "1455872655"
datestamp = "1592294437"
@@ -51,6 +51,15 @@ function imagecache_coloractions_image_effect_info() {
'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.'),
@@ -67,7 +76,6 @@ function imagecache_coloractions_image_effect_info() {
'effect callback' => 'coloractions_invert_effect',
'dimensions passthrough' => TRUE,
'form callback' => 'coloractions_invert_form',
'summary theme' => 'coloractions_invert_summary',
);
// @todo Convert may need a little more work.
@@ -116,6 +124,15 @@ function imagecache_coloractions_image_effect_info() {
'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;
}
@@ -132,6 +149,9 @@ function imagecache_coloractions_theme() {
'coloractions_coloroverlay_summary' => array(
'variables' => array('data' => NULL),
),
'coloractions_colormultiply_summary' => array(
'variables' => array('data' => NULL),
),
'coloractions_brightness_summary' => array(
'variables' => array('data' => NULL),
),
@@ -151,6 +171,9 @@ function imagecache_coloractions_theme() {
'coloractions_desaturatealpha_summary' => array(
'variables' => array('data' => NULL),
),
'coloractions_removeanimation_summary' => array(
'variables' => array('data' => NULL),
),
);
}
@@ -215,7 +238,7 @@ function coloractions_colorshift_form(array $data) {
desaturate (greyscale) it before colorizing.
The hue (color wheel) is the <em>direction</em> the
existing colors are shifted. The tone (inner box) is the amount.
Keep the tone half-way up the left site of the color box
Keep the tone half-way up the left side of the color box
for best results.
</p>"));
return $form;
@@ -300,7 +323,7 @@ function image_imagemagick_colorshift(stdClass $image, array $data) {
function coloractions_coloroverlay_form(array $data) {
$defaults = array(
'RGB' => array(
'HEX' => '#E2DB6A',
'HEX' => '',
),
);
$data = array_merge($defaults, (array) $data);
@@ -313,7 +336,7 @@ function coloractions_coloroverlay_form(array $data) {
desaturate (greyscale) it before colorizing.
The hue (color wheel) is the <em>direction</em> the
existing colors are shifted. The tone (inner box) is the amount.
Keep the tone half-way up the left site of the color box
Keep the tone half-way up the left side of the color box
for best results.
</p>"));
return $form;
@@ -411,6 +434,126 @@ function image_imagemagick_coloroverlay(stdClass $image, array $data) {
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("<p>
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 <em>direction</em> 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.
</p>"));
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.
@@ -545,16 +688,17 @@ function image_gd_invert(stdClass $image/*, array $data*/) {
/**
* Imagemagick toolkit specific implementation of the image invert effect.
*
* param stdClass $image
* @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*/) {
// @todo
return FALSE;
function image_imagemagick_invert(stdClass $image/*, array $data*/) {
// http://www.imagemagick.org/script/command-line-options.php?#negate
$image->ops[] = "-negate";
return TRUE;
}
@@ -1100,3 +1244,89 @@ function image_gd_desaturatealpha(stdClass $image/*, array $data*/) {
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' => "<p><strong>There are no user-configurable options for this effect.</strong></p>
<p>This image effect will remove all animation from a gif image, keeping only the first frame. Some notes:</p>
<ul>
<li>GD cannot handle animation at all and will always silently remove animation, whether this effect has been added to an image style or not.</li>
<li>Thus, this effect will only do something with ImageMagick as image toolkit.</li>
<li>Non gif images and non-animated gif images are silently ignored.</li>
<li>You can place this effect anywhere in the list of effects for an image style. There may be some performance gains when you add it as first though.</li>
</ul>
",
);
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;
}
@@ -58,8 +58,9 @@ record so by changing $image->info['width'] and/or $image->info['height'].
General
-------
To ease your task, this effect makes some information regarding the image being
processed available in 2 variables: $image and $image_context. These variables
are readily available in your snippet.
processed available in a number of variables: $image, $image_context,
$image_style, and $image_effect_id. These variables are readily available in
your snippet.
$image is an object containing the following properties:
- source: string, the source of the image, e.g. public://photo.jpg
@@ -112,6 +113,18 @@ $image_context is an associative array containing:
- title (string) ...
- ...
$image_style is an associative array containing the current image style being
processed. It ocntians a.o.:
- isid: the unique image style id
- name: machine name.
- label: Human readable name.
- effects: An array with the effects of this image style, ordered in the way
they should be applied.
$image_effect_id is an int containng the unique id of the current image effect
being applied. This can be used to look the current image effect up in the
$image_style array.
Of course there are many other possible useful globals. Think of:
- base_url
- base_path
@@ -143,3 +156,41 @@ foreach ($referring_entities as $field_name => $field_referring_entities) {
}
}
?>
"Dynamic" parameters
--------------------
Thee are many requests for adding token support or allowing for dynamic
parameters in another way. However, the current image style processing does not
easily allow for this. But for these cases we have the custom action to our
rescue. It is quite easy to:
- Create you own array of parameters.
- Call the effect callback yourself
Exanple, calling the watermark/canvas overlay effect:
<?php
$data = array(
'xpos' => 'center',
'ypos' => 'center',
'alpha' => '100',
'scale' => '',
'path' => 'module://imagecache_actions/tests/black-ribbon.gif',
);
return canvasactions_file2canvas_effect($image, $data);
?>
Or, to be on the safe side with effect info altering:
<?php
$definition = image_effect_definition_load('canvasactions_file2canvas');
$callback = $definition['effect callback'];
if (function_exists($callback)) {
$data = array(
'xpos' => 'center',
'ypos' => 'center',
'alpha' => '100',
'scale' => '',
'path' => 'module://imagecache_actions/tests/black-ribbon.gif',
);
return $callback($image, $data);
}
return FALSE;
?>
@@ -6,9 +6,8 @@ core = 7.x
dependencies[] = imagecache_actions
dependencies[] = image
; Information added by Drupal.org packaging script on 2016-02-19
version = "7.x-1.7"
; Information added by Drupal.org packaging script on 2020-06-16
version = "7.x-1.12"
core = "7.x"
project = "imagecache_actions"
datestamp = "1455872655"
datestamp = "1592294437"
@@ -156,7 +156,6 @@ function theme_imagecache_customactions_summary(/*array $variables*/) {
* true on success, false otherwise.
*/
function imagecache_customactions_effect(stdClass $image, array $data) {
// Check that the PHP filter module is enabled.
$result = module_exists('php');
if ($result) {
// Get context about the image.
@@ -164,10 +163,17 @@ function imagecache_customactions_effect(stdClass $image, array $data) {
$GLOBALS['image_context'] = imagecache_actions_get_image_context($image, $data);
$GLOBALS['image'] = $image;
// Get (non-alterable) context about the image style and image effect.
$execution_info = imagecache_actions_get_image_effect_context();
$GLOBALS['image_style'] = $execution_info['image_style'];
$GLOBALS['image_effect_id'] = $execution_info['image_effect_id'];
$result = php_eval('<' . '?php global $image, $image_context; ' . $data['php'] . ' ?' . '>');
// php_eval returns '1' if the snippet returns true.
$result = $result === '1';
unset($GLOBALS['image_effect_id']);
unset($GLOBALS['image_style']);
unset($GLOBALS['image']);
unset($GLOBALS['image_context']);
}
@@ -150,8 +150,9 @@ PHP snippets to determine the text
----------------------------------
Given the correct permission, you can write your own PHP snippet to compute the
text to display. To ease this task, this module makes some information regarding
the image being processed available in 2 variables: $image and $image_context.
These variables are readily available in your snippet.
the image being processed available in a number of variables: $image,
$image_context, $image_style, and $image_effect_id. These variables are readily
available in your snippet.
$image is an object containing the following properties:
- source: string, the source of the image, e.g. public://photo.jpg
@@ -214,6 +215,18 @@ $image_context is an associative array containing:
- title (string) ...
- ...
$image_style is an associative array containing the current image style being
processed. It ocntians a.o.:
- isid: the unique image style id
- name: machine name.
- label: Human readable name.
- effects: An array with the effects of this image style, ordered in the way
they should be applied.
$image_effect_id is an int containng the unique id of the current image effect
being applied. This can be used to look the current image effect up in the
$image_style array.
Of course there are many other possible useful globals. Think of:
- base_url
- base_path
@@ -66,15 +66,6 @@ function image_effects_text_form_inc(array $data) {
// - Change the 'PHP code' textarea.
$allow_php = module_exists('php') && user_access('use PHP for settings');
$defaults = array(
'size' => 50,
'angle' => 0,
'xpos' => '0',
'ypos' => '0',
'halign' => 'left',
'valign' => 'top',
'RGB' => array('HEX' => '#000000'),
'alpha' => 100,
'fontfile' => drupal_get_path('module', 'image_effects_text') . '/Komika_display.ttf',
'text_source' => 'text',
'text' => t('Hello World!'),
'php' => 'if (!$image_context[\'entity\']) {
@@ -88,77 +79,21 @@ if ($field) {
return isset($field[0][\'value\']) ? $field[0][\'value\'] : \'' . t('No field value') . '\';
}
',
'text_case' => 'none',
'fontfile' => drupal_get_path('module', 'image_effects_text') . '/Komika_display.ttf',
'size' => 50,
'RGB' => array('HEX' => '#000000'),
'alpha' => 100,
'xpos' => '0',
'ypos' => '0',
'halign' => 'left',
'valign' => 'top',
'angle' => 0,
);
$data += $defaults;
$tokens = token_info();
$tokens = array_keys($tokens['types']);
$form = array(
'size' => array(
'#type' => 'textfield',
'#title' => t('Font size'),
'#default_value' => $data['size'],
'#description' => t('The font size in points. Only in GD1 this is in pixels.'),
'#size' => 3,
),
'xpos' => array(
'#type' => 'textfield',
'#title' => t('X offset'),
'#default_value' => $data['xpos'],
'#description' => t('Enter an offset in pixels or use a keyword: <em>left</em>, <em>center</em>, or <em>right</em>. Syntax like <em>right-20</em> is also valid.'),
'#size' => 10,
),
'ypos' => array(
'#type' => 'textfield',
'#title' => t('Y offset'),
'#default_value' => $data['ypos'],
'#description' => t('Enter an offset in pixels or use a keyword: <em>top</em>, <em>center</em>, or <em>bottom</em>. Syntax like <em>bottom-20</em> is also valid.'),
'#size' => 10,
),
'halign' => array(
'#type' => 'select',
'#title' => t('Horizontal alignment'),
'#default_value' => $data['halign'],
'#description' => t('The horizontal alignment of the text around the given %xpos.', array('%xpos' => t('X offset'))),
'#options' => array(
'left' => t('Left'),
'center' => t('Center'),
'right' => t('Right')
),
),
'valign' => array(
'#type' => 'select',
'#title' => t('Vertical alignment'),
'#default_value' => $data['valign'],
'#description' => t('The vertical alignment of the text around the given %ypos.', array('%ypos' => t('Y offset'))),
'#options' => array(
'top' => t('Top'),
'center' => t('Center'),
'bottom' => t('Bottom')
),
),
'RGB' => imagecache_rgb_form($data['RGB']),
'alpha' => array(
'#type' => 'textfield',
'#title' => t('Opacity'),
'#default_value' => $data['alpha'] ? $data['alpha'] : 100,
'#size' => 3,
'#description' => t('Opacity: 1-100.'),
),
'angle' => array(
'#type' => 'textfield',
'#title' => t('Angle'),
'#default_value' => $data['angle'],
'#description' => t('Angle: The angle in degrees, with 0 degrees being left-to-right reading text. Higher values represent a counter-clockwise rotation. For example, a value of 90 would result in bottom-to-top reading text.'),
'#size' => 3,
),
'fontfile' => array(
'#type' => 'textfield',
'#title' => t('Font file name'),
'#default_value' => $data['fontfile'],
'#description' => imagecache_actions_file_field_description(),
'#element_validate' => array('image_effects_text_validate_font'),
'#size' => 80,
),
'text_help' => array(
'#type' => 'item',
'#title' => t('Text'),
@@ -221,6 +156,85 @@ if ($field) {
),
'#wysiwyg' => FALSE,
),
'text_case' => array(
'#title' => t('Capitalization'),
'#type' => 'select',
'#options' => array(
'none' => t('No transform'),
'upper' => t('Upper case'),
'lower' => t('Lower case'),
'ucfirst' => t('Capitalize first letter'),
'ucwords' => t('Capitalize each word'),
),
'#default_value' => $data['text_case'],
'#description' => t('Defines if and how to transform the case of the text to render.'),
),
'fontfile' => array(
'#type' => 'textfield',
'#title' => t('Font file name'),
'#default_value' => $data['fontfile'],
'#description' => imagecache_actions_file_field_description(),
'#element_validate' => array('image_effects_text_validate_font'),
'#size' => 80,
),
'size' => array(
'#type' => 'textfield',
'#title' => t('Font size'),
'#default_value' => $data['size'],
'#description' => t('The font size in points. Only in GD1 this is in pixels.'),
'#size' => 3,
),
'RGB' => imagecache_rgb_form($data['RGB']),
'alpha' => array(
'#type' => 'textfield',
'#title' => t('Opacity'),
'#default_value' => $data['alpha'],
'#size' => 3,
'#description' => t('Opacity: 1-100.'),
),
'xpos' => array(
'#type' => 'textfield',
'#title' => t('X offset'),
'#default_value' => $data['xpos'],
'#description' => t('Enter an offset in pixels or use a keyword: <em>left</em>, <em>center</em>, or <em>right</em>. Syntax like <em>right-20</em> is also valid.'),
'#size' => 10,
),
'ypos' => array(
'#type' => 'textfield',
'#title' => t('Y offset'),
'#default_value' => $data['ypos'],
'#description' => t('Enter an offset in pixels or use a keyword: <em>top</em>, <em>center</em>, or <em>bottom</em>. Syntax like <em>bottom-20</em> is also valid.'),
'#size' => 10,
),
'halign' => array(
'#type' => 'select',
'#title' => t('Horizontal alignment'),
'#default_value' => $data['halign'],
'#description' => t('The horizontal alignment of the text around the given %xpos.', array('%xpos' => t('X offset'))),
'#options' => array(
'left' => t('Left'),
'center' => t('Center'),
'right' => t('Right')
),
),
'valign' => array(
'#type' => 'select',
'#title' => t('Vertical alignment'),
'#default_value' => $data['valign'],
'#description' => t('The vertical alignment of the text around the given %ypos.', array('%ypos' => t('Y offset'))),
'#options' => array(
'top' => t('Top'),
'center' => t('Center'),
'bottom' => t('Bottom')
),
),
'angle' => array(
'#type' => 'textfield',
'#title' => t('Angle'),
'#default_value' => $data['angle'],
'#description' => t('Angle: The angle in degrees, with 0 degrees being left-to-right reading text. Higher values represent a counter-clockwise rotation. For example, a value of 90 would result in bottom-to-top reading text.'),
'#size' => 3,
),
);
if (!$allow_php && $data['text_source'] !== 'php') {
unset($form['text_fieldset']['text_source']['#options']['php']);
@@ -717,12 +731,64 @@ function image_effects_text_get_text(stdClass $image, array $data) {
// variables, so they can be passed successfully.
$GLOBALS['image_context'] = $image_context;
$GLOBALS['image'] = $image;
// Get (non-alterable) context about the image style and image effect.
$execution_info = imagecache_actions_get_image_effect_context();
$GLOBALS['image_style'] = $execution_info['image_style'];
$GLOBALS['image_effect_id'] = $execution_info['image_effect_id'];
// We don't need to check_plain() the resulting text, as the text is not
// rendered in a browser but processed on the server.
$text = module_exists('php') ? php_eval('<' . '?php global $image, $image_context; ' . $data['php'] . ' ?' . '>') : '';
unset($GLOBALS['image_effect_id']);
unset($GLOBALS['image_style']);
unset($GLOBALS['image']);
unset($GLOBALS['image_context']);
}
// Convert case.
$text = image_effect_text_case_transform($text, isset($data['text_case']) ? $data['text_case'] : 'none');
return $text;
}
/**
* Transform a string by a certain method.
*
* Proudly copied from module://views/includes/handlers.inc.
*
* @param $string
* The input you want to transform.
* @param $option
* How do you want to transform it, possible values:
* - upper: Uppercase the string.
* - lower: lowercase the string.
* - ucfirst: Make the first char uppercase.
* - ucwords: Make each word in the string uppercase.
*
* @return string
* The transformed string.
*/
function image_effect_text_case_transform($string, $option) {
global $multibyte;
switch ($option) {
default:
return $string;
case 'upper':
return drupal_strtoupper($string);
case 'lower':
return drupal_strtolower($string);
case 'ucfirst':
return drupal_strtoupper(drupal_substr($string, 0, 1)) . drupal_substr($string, 1);
case 'ucwords':
if ($multibyte == UNICODE_MULTIBYTE) {
return mb_convert_case($string, MB_CASE_TITLE);
}
else {
return ucwords($string);
}
}
}
@@ -6,9 +6,8 @@ core = 7.x
dependencies[] = image
dependencies[] = imagecache_actions
; Information added by Drupal.org packaging script on 2016-02-19
version = "7.x-1.7"
; Information added by Drupal.org packaging script on 2020-06-16
version = "7.x-1.12"
core = "7.x"
project = "imagecache_actions"
datestamp = "1455872655"
datestamp = "1592294437"
@@ -8,9 +8,8 @@ dependencies[] = image_effects_text
dependencies[] = imagecache_canvasactions
dependencies[] = system_stream_wrapper
; Information added by Drupal.org packaging script on 2016-02-19
version = "7.x-1.7"
; Information added by Drupal.org packaging script on 2020-06-16
version = "7.x-1.12"
core = "7.x"
project = "imagecache_actions"
datestamp = "1455872655"
datestamp = "1592294437"
@@ -1,5 +1,5 @@
README for the Image styleds admin Drupal module
------------------------------------------------
README for the Image styles admin Drupal module
-----------------------------------------------
Project page: https://drupal.org/project/imagecache_actions
@@ -14,14 +14,13 @@ Release notes for 7.x-1.x-dev
Introduction
------------
The Image style admin module extends the administrative interface for image
The Image styles admin module extends the administrative interface for image
styles by providing additional features.
Currently a duplicate, import and export image style feature are implemented.
More features may be added in the future. These features typically allow you to
more easily handle image styles. It allows us to more easily set up
a test/showcase sute of styles. Finally, it allows everybody to test D8 image
module features in real life.
more easily handle image styles. It allows us to more easily set up a
test/showcase suite of styles.
This module is not a replacement for the features module
(https://drupal.org/project/features). If you are serious about configuration
@@ -30,9 +29,3 @@ management and want to distribute styles to other systems, use features.
Use this module for 1 time export/imports between different sites, "copy &
paste" reuse within a site, and when reporting issues to the imagecache_actions
issue queue.
TODO
----
Solving errors in the core image handling?
- [#1554074]: scale does not work with imagemagick when dimensions are unknown?
@@ -117,7 +117,7 @@ function image_styles_admin_export_form($form, $form_state, $style) {
'#type' => 'textarea',
'#rows' => 5,
'#title' => t('Image style export data'),
'#default_value' => image_styles_admin_export_serialize($style),
'#default_value' => image_styles_admin_style_to_string($style),
'#attributes' => array('readonly' =>'readonly'),
'#description' => t('Copy the contents of this field to the clipboard and, on another site, paste it in the textarea of an %page_title page.',
array('%page_title' => t('Import image style'))),
@@ -178,16 +178,16 @@ function image_styles_admin_import_form_submit($form, &$form_state) {
}
/**
* Serializes image style data so it can be exported.
* Converts image style data into a json string so it can be exported.
*
* @param array $style
* An image style array.
*
* @return string
* The serialized image style. Keys that are not needed for import are not
* serialized.
* The image style converted to a string. Keys that are not needed for import
* are not serialized.
*/
function image_styles_admin_export_serialize($style) {
function image_styles_admin_style_to_string($style) {
$style = array_intersect_key($style, array('name' => 0, 'label' => 0, 'effects' => 0));
foreach ($style['effects'] as &$effect) {
$effect = array_intersect_key($effect, array('weight' => 0, 'name' => 0, 'data' => 0));
@@ -198,16 +198,14 @@ function image_styles_admin_export_serialize($style) {
$value = image_styles_admin_unify_newlines($value);
}
});
return serialize($style);
return json_encode($style);
}
/**
* Unifies newlines in the string to the Unix newline standard.
*
* #2636314: textareas may convert newlines to the underlying OS style: convert
* all new lines to Unix style before unserializing. As string length is in the
* serialized data, we must ensure that we also do this on each array value
* before serializing.
* all new lines to Unix style before stringifying an image style.
*
* @param string $str
*
@@ -220,17 +218,41 @@ function image_styles_admin_unify_newlines($str) {
}
/**
* Unserializes and validates a string into image style data.
* Decodes and validates a json string into image style data.
*
* Some notes on any security implications for creating styles like this:
* - json_decode() is considered safe regardless of the contents give to it.
* - Not expected array entries are subsequently removed (array_intersect_key)
* thus the return will not contain unexpected array entries
* - Values with known types are checked.
* - Effect data array is not checked as it cannot be checked. Possibly unsafe
* but:
* - Proper checking and/or converting to int/float/bool while processing an
* image derivative is the responsibility of the image effect.
* - Effect data is only shown to a user on the edit form and in the image
* effect summary theme. Proper escaping and/or converting to int/float/bool
* in the theme before rendering it is again the responsibility of the image
* effect. On the form it is the form api that will do so.
* - Effect data may contain PHP code and if the image effect is allowing this
* it may get [php_]eval()'ed. The image effects themselves should check for
* the 'use PHP for settings' permission on the create/edit form and check
* that the PHP module is enabled on execution (that is: during image
* derivative creation).
* However, we cannot do so on importing as we cannot know if the imported
* image style contains image effects that allow PHP code. Therefore, we use a
* separate access right for importing styles that is to be considered having
* the same security implications as the 'use PHP for settings' right (from
* the PHP module) and thus should only be given to highly trusted users.
*
* @param string $import
* The string representation of a @see serialize()'d image style array.
* The json representation of an image style array.
*
* @return array|false
* An image style array or false if the string could not be unserialized into
* An image style array or false if the string could not be decoded into
* image style data.
*/
function image_styles_admin_import_extract_style($import) {
$style = unserialize($import);
$style = json_decode($import, TRUE);
// Check if the contents of the textarea could be unserialized into an array.
if (!is_array($style)) {
@@ -280,30 +302,5 @@ function image_styles_admin_import_extract_style($import) {
}
}
// @todo: are there any security implications for creating styles like this?
// - Unserialize() is save in itself: it only creates data (except possibly
// for__wakeup(), but that can only be in already existing code: safe
// - Not expected array entries are removed (array_intersect_key): safe
// - Basic types are checked: safe
// - Effect data array is not checked. Possibly unsafe?! The effect data array
// contains the effect parameters. Normally these are entered and validated
// via a form and subsequently saved in the database (serialized as here).
// The form validation is not executed on import and thus the data may
// contain invalid values. This is acceptable as it can also be done by
// operating directly on the database. In Drupal this is not normally
// checked for during processing: error messages will make clear that the
// data has been played with. Can incorrect data be abused? It may contain:
// - incorrect types: we do not know the data structure of the various
// effects, so we cannot check that and have to accept it as it comes.
// Effects should check_plain in summary theme and convert to int/float
// whenever possible before using it in commands.
// - PHP code, but that may be valid content for the text or custom effects:
// Effects should check_plain in summary theme and convert to int/float
// whenever possible before using it in commands.
// @todo: if the style contains an effect that contains PHP code, the user
// should need the 'use PHP for settings' permission.
// - HTML and or JS code: when used as parameter, this normally won't hurt.
// When showing on the screen (summary theme), proper escaping should
// suffice and is needed anyway: responsibility of effect.
return $style;
}
@@ -5,9 +5,8 @@ core = 7.x
dependencies[] = image
; Information added by Drupal.org packaging script on 2016-02-19
version = "7.x-1.7"
; Information added by Drupal.org packaging script on 2020-06-16
version = "7.x-1.12"
core = "7.x"
project = "imagecache_actions"
datestamp = "1455872655"
datestamp = "1592294437"
@@ -0,0 +1,8 @@
<?php
/**
* Rebuild the menu.
*/
function image_styles_admin_update_7001(&$sandbox) {
menu_rebuild();
}
@@ -3,6 +3,25 @@
* @file Hook and callback implementations that must be available at all times.
*/
/**
* Implements hook_permission().
*/
function image_styles_admin_permission() {
return array(
'import image styles' => array(
'title' => t('Import image styles.') ,
'restrict access' => TRUE,
),
);
}
/**
* Determines access to the import image style form for the current user.
*/
function image_styles_admin_access() {
return user_access('import image styles') && user_access('administer image styles');
}
/**
* Implements hook_menu().
*/
@@ -29,7 +48,7 @@ function image_styles_admin_menu() {
'description' => 'Import an image style.',
'page callback' => 'drupal_get_form',
'page arguments' => array('image_styles_admin_import_form'),
'access arguments' => array('administer image styles'),
'access callback' => 'image_styles_admin_access',
'type' => MENU_LOCAL_ACTION,
'weight' => 3,
'file' => 'image_styles_admin.inc',
@@ -41,9 +60,15 @@ function image_styles_admin_menu() {
* Implements hook_preprocess_HOOK for theme image_style_list.
*/
function image_styles_admin_preprocess_image_style_list(&$variables) {
// Sort the image styles by name.
uasort($variables['styles'], function ($a, $b) {
return strcasecmp($a['label'], $b['label']);
});
// Tell imagecache_actions_preprocess_table to preprocess the next call to
// theme_table()
// theme_table().
$image_styles = array_values($variables['styles']);
image_styles_admin_preprocess_table($image_styles);
// Add CSS and JS files.
@@ -5,9 +5,8 @@ core = 7.x
dependencies[] = image
; Information added by Drupal.org packaging script on 2016-02-19
version = "7.x-1.7"
; Information added by Drupal.org packaging script on 2020-06-16
version = "7.x-1.12"
core = "7.x"
project = "imagecache_actions"
datestamp = "1455872655"
datestamp = "1592294437"
@@ -8,9 +8,8 @@ dependencies[] = system_stream_wrapper
features[image][] = 'corners_combo'
; Information added by Drupal.org packaging script on 2016-02-19
version = "7.x-1.7"
; Information added by Drupal.org packaging script on 2020-06-16
version = "7.x-1.12"
core = "7.x"
project = "imagecache_actions"
datestamp = "1455872655"
datestamp = "1592294437"
@@ -19,8 +19,8 @@ function imagecache_actions_pos_form(array $data) {
);
$data = array_merge($defaults, (array) $data);
$description1 = t('Enter an offset in pixels (e.g. 10, 10px), a percentage (e.g. 25%), or one of the keywords: <em>left</em>, <em>center</em>, or <em>right</em> wih an optional offset (e.g. center, right - 10%).');
$description2 = t('Enter an offset in pixels (e.g. 10, 10px), a percentage (e.g. 25%), or one of the keywords: <em>top</em>, <em>center</em>, or <em>bottom</em> wih an optional offset (e.g. center, bottom - 10%).');
$description1 = t('Enter an offset in pixels (e.g. 10, 10px), a percentage (e.g. 25%), or one of the keywords: <em>left</em>, <em>center</em>, or <em>right</em> with an optional offset (e.g. center, right - 10%).');
$description2 = t('Enter an offset in pixels (e.g. 10, 10px), a percentage (e.g. 25%), or one of the keywords: <em>top</em>, <em>center</em>, or <em>bottom</em> with an optional offset (e.g. center, bottom - 10%).');
$form = array(
'xpos' => array(
'#type' => 'textfield',
@@ -143,7 +143,7 @@ function imagecache_actions_get_style_label($style_name) {
}
/**
* Return an array with context information about the image.
* Returns an array with context information about the image.
*
* This information is called by effects that need contextual information or
* that allow custom PHP:
@@ -192,27 +192,33 @@ function imagecache_actions_get_image_context($image, $data) {
+ file_get_file_references($managed_file, NULL, FIELD_LOAD_CURRENT, 'file');
if ($references) {
// Load referring entities.
// Load referring entities., keep track of 1st reference separately.
$entity_type_first = '';
$field_name_first = '';
foreach ($references as $field_name => $field_references) {
foreach ($field_references as $entity_type => $entity_stubs) {
$image_context['referring_entities'][$field_name][$entity_type] = entity_load($entity_type, array_keys($entity_stubs));
$entities = entity_load($entity_type, array_keys($entity_stubs));
if (!empty($entities)) {
$image_context['referring_entities'][$field_name][$entity_type] = $entities;
// Make it easy to access the '1st' entity and its referring image
// field, part 1: entity.
if (empty($entity_type_first)) {
$entity_type_first = $entity_type;
$field_name_first = $field_name;
$image_context['entity'] = reset($entities);
}
}
}
}
// Make it easy to access the '1st' entity and its referring image field.
reset($image_context['referring_entities']);
list($field_name, $field_references) = each($image_context['referring_entities']);
reset($field_references);
list($entity_type, $entities) = each($field_references);
reset($entities);
list(, $image_context['entity']) = each($entities);
// Make it easy to access the '1st' entity and its referring image field,
// part 2: image field.
/** @var array|false $image_field */
$image_field = field_get_items($entity_type, $image_context['entity'], $field_name);
$image_field = field_get_items($entity_type_first, $image_context['entity'], $field_name_first);
if ($image_field) {
// Get referring item.
foreach ($image_field as $image_field_value) {
if ($image_field_value['fid'] === $managed_file->fid) {
// @todo: file_field as well or just ignore the type of field?
$image_context['image_field'] = $image_field_value;
break;
}
@@ -224,6 +230,53 @@ function imagecache_actions_get_image_context($image, $data) {
return $image_context;
}
/**
* Returns information about the context, image style and image effect id, of
* the current image effect.
*
* Note that this information is not alterable, that is, it will not have any
* effect on the rest of the derivative image generation process including its
* subsequent effects.
*
* This information is called by effects that may need contextual information or
* that allow custom PHP:
* - Custom action.
* - Text from image alt or title.
* - Text with tokens.
* - Text from PHP code.
*
* This information is fetched by traversing the backtrace, looking for known
* functions that have the image style or effect as argument..
*
* @return array
* Array with keys 'image_style' and 'image_effect_id' pointing to the current
* image style (array) resp. image effect id (int).
*/
function imagecache_actions_get_image_effect_context() {
$result = array();
$backtrace = debug_backtrace();
foreach ($backtrace as $function_call) {
if ($function_call['function'] && $function_call['function'] === 'image_effect_apply') {
$result['image_effect_id'] = isset($function_call['args'][1]['ieid']) ? $function_call['args'][1]['ieid'] : NULL;
}
else if ($function_call['function'] && $function_call['function'] === 'image_style_create_derivative') {
$result['image_style'] = isset($function_call['args'][0]) ? $function_call['args'][0] : NULL;
}
if (count($result) === 2) {
break;
}
}
$result += array(
'image_effect_id' => NULL,
'image_style' => NULL,
);
return $result;
}
/**
* Given two imageapi objects with dimensions, and some positioning values,
* calculate a new x,y for the layer to be placed at.
@@ -369,11 +422,14 @@ function imagecache_actions_calculate_offset($keyword, $value, $base_size, $laye
}
// Handle % values.
if (substr($value, strlen($value) - 1, 1) == '%') {
$value = intval($value / 100 * $base_size);
if (substr($value, -1) === '%') {
// Explicitly convert $value to prevent warnings in PHP 7.1.
$value = intval((int) $value / 100 * $base_size);
$offset = -1 * ($layer_size / 2);
}
$value = $base + ($direction * $value);
// $value may contain 'px' at the end: explicitly convert to prevent warnings
// in PHP 7.1.
$value = $base + ($direction * (int) $value);
// Add any extra offset to position the item.
return $value + $offset;
@@ -389,16 +445,16 @@ function imagecache_actions_calculate_offset($keyword, $value, $base_size, $laye
* A string specifing an RGB color in the formats:
* '#ABC','ABC','#ABCD','ABCD','#AABBCC','AABBCC','#AABBCCDD','AABBCCDD'
*
* @return array
* @return array|false
* An array with four elements for red, green, blue, and alpha.
*/
function imagecache_actions_hex2rgba($hex) {
$hex = ltrim($hex, '#');
if (preg_match('/^[0-9a-f]{3}$/i', $hex)) {
// 'FA3' is the same as 'FFAA33' so r=FF, g=AA, b=33
$r = str_repeat($hex{0}, 2);
$g = str_repeat($hex{1}, 2);
$b = str_repeat($hex{2}, 2);
$r = str_repeat($hex[0], 2);
$g = str_repeat($hex[1], 2);
$b = str_repeat($hex[2], 2);
$a = '0';
}
elseif (preg_match('/^[0-9a-f]{6}$/i', $hex)) {
@@ -412,10 +468,10 @@ function imagecache_actions_hex2rgba($hex) {
}
elseif (preg_match('/^[0-9a-f]{4}$/i', $hex)) {
// 'FA37' is the same as 'FFAA3377' so r=FF, g=AA, b=33, a=77
$r = str_repeat($hex{0}, 2);
$g = str_repeat($hex{1}, 2);
$b = str_repeat($hex{2}, 2);
$a = str_repeat($hex{3}, 2);
$r = str_repeat($hex[0], 2);
$g = str_repeat($hex[1], 2);
$b = str_repeat($hex[2], 2);
$a = str_repeat($hex[3], 2);
}
else {
// error: invalid hex string, @todo: set form error.
@@ -468,12 +524,13 @@ function imagecache_actions_keyword_filter($value, $base_size, $layer_size) {
* Computes a length based on a length specification and an actual length.
*
* Examples:
* (50, 400) returns 50; (50%, 400) returns 200;
* (50, 400) returns 50; (50px, 400) returns 50; (50%, 400) returns 200;
* (50, null) returns 50; (50%, null) returns null;
* (null, null) returns null; (null, 100) returns null.
*
* @param string|null $length_specification
* The length specification. An integer constant or a % specification.
* The length specification. An integer constant optionally followed by 'px'
* or '%'.
* @param int|null $current_length
* The current length. May be null.
*
@@ -483,5 +540,9 @@ function imagecache_actions_percent_filter($length_specification, $current_lengt
if (strpos($length_specification, '%') !== FALSE) {
$length_specification = $current_length !== NULL ? str_replace('%', '', $length_specification) * 0.01 * $current_length : NULL;
}
else {
// Strips 'px' if available.
$length_specification = (int) $length_specification;
}
return $length_specification;
}