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', + ), ); }