diff --git a/canvasactions/canvasactions.inc b/canvasactions/canvasactions.inc
index dd599d6..1c8ae0a 100644
--- a/canvasactions/canvasactions.inc
+++ b/canvasactions/canvasactions.inc
@@ -1247,3 +1247,470 @@ 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,
+ 'aspect_coefficient' => 0.85,
+ );
+ $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 on both sides depending on the entered distortion value. If asymmetrical, you have to set distortion values for both sides.'),
+ '#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('The distortion of the vanish. The closer this parameter is to 50%, the more stretched the part of the image that represents the foreground will become. For a pleasing effect, you should choose a number between 0 and 35%'),
+ );
+
+ $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('The opposite distortion of the vanish point. The closer this parameter is to 50%, the more stretched the part of the image that represents the foreground will become. For a pleasing effect, you should choose a number between 0 and 35%'),
+ );
+
+ $form['aspect_coefficient'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Width/height Aspect coefficient'),
+ '#size' => 6,
+ '#default_value' => $data['aspect_coefficient'],
+ '#element_validate' => array('canvasactions_perspective_aspect_coefficient_validate'),
+ '#description' => t('Coefficient for change result image width/height after perspective transformation.'),
+ );
+
+ return $form;
+}
+
+/**
+ * Form element validation handler for aspect_coefficient.
+ */
+function canvasactions_perspective_aspect_coefficient_validate($element, &$form_state) {
+ if (!is_numeric($element['#value']) || $element['#value'] < 0 || $element['#value'] > 1) {
+ form_error($element, t('!name must be a value between 0 and 1.', array('!name' => $element['#title'])));
+ }
+}
+
+/**
+ * Form element validation handler for distortion.
+ */
+function canvasactions_perspective_distortion_validate($element, &$form_state) {
+ if (!is_numeric($element['#value']) || $element['#value'] < 0 || $element['#value'] >= 50) {
+ form_error($element, t('!name must be a value between 0 and less than 50.', array('!name' => $element['#title'])));
+ }
+}
+
+/**
+ * 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 = t(' @symmetry. Vanishing point position: @vanish.', array(
+ '@symmetry' => $data['symmetry'],
+ '@vanish' => $data['vanish'],
+ ));
+
+ if ($data['symmetry'] == 'asymmetrical') {
+ switch ($data['vanish']) {
+ case "top":
+ case "bottom":
+ $output .= " Left distort: {$data['distortion']}%, right distort: {$data['opposite_distortion']}%. ";
+ break;
+
+ case "right":
+ case "left":
+ $output .= " Top distort: {$data['distortion']}%, bottom distort: {$data['opposite_distortion']}%. ";
+ break;
+ }
+ }
+ else {
+ $output .= " Distort: {$data['distortion']}%. ";
+ }
+ $output .= "Aspect coefficient: {$data['aspect_coefficient']}";
+
+ return $output;
+}
+
+/**
+ * Image effect callback for the Perspective effect.
+ *
+ * @param stdClass $image
+ * An image object returned by image_load().
+ * @param array $data
+ *
+ * @return bool
+ * true on success, false otherwise.
+ */
+function canvasactions_perspective_effect(stdClass $image, array $data) {
+ $coefficient = $data['aspect_coefficient'];
+ $targetsize = array();
+
+ switch ($data['vanish']) {
+ case "top":
+ case "bottom":
+ $targetsize['height'] = (int) round($image->info['height'] * $coefficient);
+ $targetsize['width'] = $image->info['width'];
+ break;
+
+ case "right":
+ case "left":
+ $targetsize['width'] = (int) round($image->info['width'] * $coefficient);
+ $targetsize['height'] = $image->info['height'];
+ break;
+ }
+
+ $data['targetsize'] = $targetsize;
+
+ 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;
+ }
+
+ $image->info['width'] = $targetsize['width'];
+ $image->info['height'] = $targetsize['height'];
+
+ 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,
+ * contain 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['opposite_distortion'];
+ $targetsize = $data['targetsize'];
+
+ if ($data['symmetry'] == 'symmetrical') {
+ $opposite_distortion = $distortion;
+ }
+
+ // Increasing the source image width/height to reduce distortion
+ $color = imagecolorallocatealpha($image->resource, 0, 0, 0, 127);
+ $multiplier = 3;
+ $multiImage = imagecreatetruecolor($width * $multiplier, $height * $multiplier);
+ imagealphablending($multiImage, FALSE);
+ imagefilledrectangle($multiImage, 0, 0, $width * $multiplier, $height * $multiplier, $color);
+ imagesavealpha($multiImage, TRUE);
+ imagecopyresized($multiImage, $image->resource, 0, 0, 0, 0, $width * $multiplier, $height * $multiplier, $width, $height);
+ imagedestroy($image->resource);
+ $width *= $multiplier;
+ $height *= $multiplier;
+
+ // Creating new large image for apply perspective effect
+ $new_image = imagecreatetruecolor($width, $height);
+ imagealphablending($new_image, FALSE);
+ imagefilledrectangle($new_image, 0, 0, $width, $height, $color);
+ imagealphablending($new_image, TRUE);
+
+ // 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($width * $distortion / 100);
+ $right = round($width - ($width * (100 - $opposite_distortion) / 100));
+
+ $tg_beta_left = $left / $height;
+ $tg_beta_right = $right / $height;
+
+ for ($y = 0; $y < $height; $y++) {
+ $new_left = ($height - $y) * $tg_beta_left;
+ $new_right = ($height - $y) * $tg_beta_right;
+ $new_width = $width - $new_left - $new_right;
+ imagecopyresampled($new_image, $multiImage,
+ $new_left, $y,
+ 0, $y,
+ $new_width, 1,
+ $width, 1);
+ }
+ break;
+
+ case "bottom":
+ $left = round($width * $distortion / 100);
+ $right = round($width - ($width * (100 - $opposite_distortion) / 100));
+
+ $tg_beta_left = $left / $height;
+ $tg_beta_right = $right / $height;
+
+ for ($y = $height; $y > 0; $y--) {
+ $new_left = $y * $tg_beta_left;
+ $new_right = $y * $tg_beta_right;
+ $new_width = $width - $new_left - $new_right;
+ imagecopyresampled($new_image, $multiImage,
+ $new_left, $y,
+ 0, $y,
+ $new_width, 1,
+ $width, 1);
+ }
+ break;
+
+ case "right":
+ $top = round($height * $distortion / 100);
+ $bottom = round($height - ($height * (100 - $opposite_distortion) / 100));
+
+ $tg_beta_top = $top / $width;
+ $tg_beta_bottom = $bottom / $width;
+
+ for ($x = $width; $x > 0; $x--) {
+ $new_top = $x * $tg_beta_top;
+ $new_bottom = $x * $tg_beta_bottom;
+ $new_height = $height - $new_top - $new_bottom;
+ imagecopyresampled($new_image, $multiImage,
+ $x, $new_top,
+ $x, 0,
+ 1, $new_height,
+ 1, $height);
+ }
+ break;
+
+ case "left":
+ $top = round($height * $distortion / 100);
+ $bottom = round($height - ($height * (100 - $opposite_distortion) / 100));
+
+ $tg_beta_top = $top / $width;
+ $tg_beta_bottom = $bottom / $width;
+
+ for ($x = 0; $x < $width; $x++) {
+ $new_top = ($width - $x) * $tg_beta_top;
+ $new_bottom = ($width - $x) * $tg_beta_bottom;
+ $new_height = $height - $new_top - $new_bottom;
+ imagecopyresampled($new_image, $multiImage,
+ $x, $new_top,
+ $x, 0,
+ 1, $new_height,
+ 1, $height);
+ }
+ break;
+ }
+ imagedestroy($multiImage);
+ imagealphablending($new_image, FALSE);
+ imagesavealpha($new_image, TRUE);
+
+ // Return image with perspective effect to original size
+ $original_size_image = imagecreatetruecolor($targetsize['width'], $targetsize['height']);
+ imagealphablending($original_size_image, FALSE);
+ imagefilledrectangle($original_size_image, 0, 0, $targetsize['width'], $targetsize['height'], $color);
+ imagealphablending($original_size_image, TRUE);
+ imagecopyresampled($original_size_image, $new_image, 0, 0, 0, 0, $targetsize['width'], $targetsize['height'], $width, $height);
+ imagedestroy($new_image);
+ imagealphablending($original_size_image, FALSE);
+ imagesavealpha($original_size_image, TRUE);
+
+ $image->resource = $original_size_image;
+
+ return TRUE;
+}
+
+/**
+ * Imagemagick 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,
+ * contain 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['opposite_distortion'];
+ $targetsize = $data['targetsize'];
+
+ if ($data['symmetry'] == 'symmetrical'){
+ $opposite_distortion = $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":
+ $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;
+ }
+
+ $perspective = escapeshellarg($perspective_arg);
+ $image->ops[] = "-matte -virtual-pixel transparent -distort Perspective {$perspective}";
+
+ // When applying '-resize' method in fact the images were only
+ // enlarged or reduced just enough so as to best fit into the given size.
+ // Need ignore the aspect ratio.
+ // This is done by adding the character '!' to the size.
+ $geometry = escapeshellarg(sprintf('%dx%d!', $targetsize['width'], $targetsize['height']));
+ $image->ops[] = "-resize {$geometry}";
+
+ return TRUE;
+}
+
+/**
+ * Image dimensions callback for the Perspective effect.
+ *
+ * @param array $dimensions
+ * Dimensions to be modified - an associative array containing the items
+ * 'width' and 'height' (in pixels).
+ * @param array $data
+ * An associative array containing the effect data.
+ */
+function canvasactions_perspective_dimensions(array &$dimensions, array $data) {
+ if ($dimensions['width'] && $dimensions['height']) {
+ $coefficient = $data['aspect_coefficient'];
+
+ switch ($data['vanish']) {
+ case "top":
+ case "bottom":
+ $dimensions['height'] = (int) round($dimensions['height'] * $coefficient);
+ break;
+
+ case "right":
+ case "left":
+ $dimensions['width'] = (int) round($dimensions['width'] * $coefficient);
+ break;
+ }
+ }
+}
+
+/**
+ * Implements theme_hook().
+ *
+ * @param $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);
+
+ $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;
+
+ return theme('table', array('header' => array(), 'rows' => $rows, 'attributes' => array('class' => array('image-anchor'))));
+}
diff --git a/canvasactions/imagecache_canvasactions.module b/canvasactions/imagecache_canvasactions.module
index 0bf772c..32e4a48 100644
--- a/canvasactions/imagecache_canvasactions.module
+++ b/canvasactions/imagecache_canvasactions.module
@@ -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 callback' => 'canvasactions_perspective_dimensions',
+ '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',
+ ),
);
}