'ImageAPI', 'description' => 'Configure ImageAPI.', 'page callback' => 'drupal_get_form', 'page arguments' => array('imageapi_settings'), 'access arguments' => array('administer imageapi'), ); $toolkits = imageapi_get_available_toolkits(); if ($toolkits) { $items['admin/settings/imageapi/list'] = array( 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -1, ); $items['admin/settings/imageapi/config'] = array( 'title' => 'Configure', 'type' => MENU_LOCAL_TASK, 'page callback' => 'drupal_get_form', 'page arguments' => array(imageapi_default_toolkit() .'_settings_form'), 'access arguments' => array('administer imageapi'), ); foreach ($toolkits as $module => $info) { if (function_exists($module .'_settings_form')) { $items['admin/settings/imageapi/config/'. $module] = array( 'title' => '@name', 'title arguments' => array('@name' => $info['name']), 'page callback' => 'drupal_get_form', 'page arguments' => array($module .'_settings_form'), 'access arguments' => array('administer imageapi'), 'type' => $module == imageapi_default_toolkit() ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, ); } else { drupal_set_message(t('ImageAPI toolkit missing settings form'), 'error'); } } } return $items; } function imageapi_settings() { $form = array(); $toolkits = imageapi_get_available_toolkits(); switch (count($toolkits)) { case 0: $form['imageapi_toolkits']['#value'] = t('There are no image toolkit modules enabled. Toolkit modules can be enabled from the module configuration page.', array('!admin-build-modules' => url('admin/build/modules'))); return $form; case 1: $toolkit = key($toolkits); // The variable needs to be manually set. Otherwise, if a user has two // toolkits and disables the selected one they won't be able to select the // remaing toolkit. variable_set('imageapi_image_toolkit', $toolkit); $form['imageapi_image_toolkit']['#value'] = t('The %toolkit module is the only enabled image toolkit. Drupal will use it for resizing, cropping and other image manipulations.', array('%toolkit' => $toolkits[$toolkit]['name'])); return $form; default: $options = array(); foreach ($toolkits as $module => $info) { $options[$module] = $info['name']; } $form['imageapi_image_toolkit'] = array( '#type' => 'radios', '#title' => t('Select a default image processing toolkit'), '#default_value' => imageapi_default_toolkit(), '#options' => $options, '#description' => t('This setting lets you choose which toolkit Drupal uses resizing, cropping and other image manipulations.'), ); } return system_settings_form($form); } /** * Return a list of available toolkits. * * @return * An array of the enabled image toolkit modules. The module name is the key * and the value is a sub-array with the module's 'name' and 'description'. */ function imageapi_get_available_toolkits() { static $toolkits; if (!isset($toolkits)) { $toolkits = array(); foreach (module_implements('imageapi_toolkit', TRUE) as $module) { $info = drupal_parse_info_file(drupal_get_path('module', $module) .'/'. $module .'.info'); $toolkits[$module] = array('name' => $info['name'], 'description' => $info['description']); } } return $toolkits; } /** * Retrieve the name of the currently used toolkit. * * @return * String containing the name of the toolkit, or FALSE if none is available. */ function imageapi_default_toolkit() { $toolkit = variable_get('imageapi_image_toolkit', 'imageapi_gd'); // Verify that the image toolkit is available. if (isset($toolkit) && module_exists($toolkit)) { return $toolkit; } // If it's not fall back to first available toolist. foreach (imageapi_get_available_toolkits() as $toolkit => $info) { return $toolkit; } return FALSE; } /** * Invokes the given method using the currently selected toolkit. * * @param $method * A string containing the method to invoke. * @param $image * An image object returned by imageapi_image_open(). * @param $params * An optional array of parameters to pass to the toolkit method. * @return * Mixed values (typically Boolean indicating successful operation). */ function imageapi_toolkit_invoke($method, &$image, array $params = array()) { $function = $image->toolkit . '_image_' . $method; if (function_exists($function)) { array_unshift($params, $image); $params[0] = &$image; return call_user_func_array($function, $params); } watchdog('imageapi', 'The selected image handling toolkit %toolkit can not correctly process %function.', array('%toolkit' => $image->toolkit, '%function' => $function), WATCHDOG_ERROR); return FALSE; } /** * Scales an image to the exact width and height given. * * This function achieves the target aspect ratio by cropping the original image * equally on both sides, or equally on the top and bottom. This function is * useful to create uniform sized avatars from larger images. * * The resulting image always has the exact target dimensions. * * @param $image * An image object returned by imageapi_image_open(). * @param $width * The target width, in pixels. * @param $height * The target height, in pixels. * @return * TRUE or FALSE, based on success. */ function imageapi_image_scale_and_crop(&$image, $width, $height) { $scale = max($width / $image->info['width'], $height / $image->info['height']); $x = ($image->info['width'] * $scale - $width) / 2; $y = ($image->info['height'] * $scale - $height) / 2; if (imageapi_image_resize($image, $image->info['width'] * $scale, $image->info['height'] * $scale)) { return imageapi_image_crop($image, $x, $y, $width, $height); } return FALSE; } /** * Scales an image to the given width and height while maintaining aspect * ratio. * * The resulting image can be smaller for one or both target dimensions. * * @param $image * An image object returned by imageapi_image_open(). * @param $width * The target width, in pixels. This value is omitted then the scaling will * based only on the height value. * @param $height * The target height, in pixels. This value is omitted then the scaling will * based only on the width value. * @param $upscale * Boolean indicating that files smaller than the dimensions will be scalled * up. This generally results in a low quality image. * @return * TRUE or FALSE, based on success. */ function imageapi_image_scale(&$image, $width = NULL, $height = NULL, $upscale = FALSE) { $aspect = $image->info['height'] / $image->info['width']; if ($upscale) { // Set width/height according to aspect ratio if either is empty. $width = !empty($width) ? $width : $height / $aspect; $height = !empty($height) ? $height : $width / $aspect; } else { // Set impossibly large values if the width and height aren't set. $width = !empty($width) ? $width : 9999999; $height = !empty($height) ? $height : 9999999; // Don't scale up. if (round($width) >= $image->info['width'] && round($height) >= $image->info['height']) { return TRUE; } } if ($aspect < $height / $width) { $height = $width * $aspect; } else { $width = $height / $aspect; } return imageapi_image_resize($image, $width, $height); } /** * Resize an image to the given dimensions (ignoring aspect ratio). * * @param $image * An image object returned by imageapi_image_open(). * @param $width * The target width, in pixels. * @param $height * The target height, in pixels. * @return * TRUE or FALSE, based on success. */ function imageapi_image_resize(&$image, $width, $height) { $width = (int) round($width); $height = (int) round($height); return imageapi_toolkit_invoke('resize', $image, array($width, $height)); } /** * Rotate an image by the given number of degrees. * * @param $image * An image object returned by imageapi_image_open(). * @param $degrees * The number of (clockwise) degrees to rotate the image. * @param $background * An hexadecimal integer specifying the background color to use for the * uncovered area of the image after the rotation. E.g. 0x000000 for black, * 0xff00ff for magenta, and 0xffffff for white. For images that support * transparency, this will default to transparent. Otherwise it will * be white. * @return * TRUE or FALSE, based on success. */ function imageapi_image_rotate(&$image, $degrees, $background = NULL) { return imageapi_toolkit_invoke('rotate', $image, array($degrees, $background)); } /** * Sharpen an image given some sharpening parameters. * * NOTE: These parameters only have an effect when Imagemagick is used. * GD will used a fixed convolution matrix as described in imageapi_gd.module * * @param $image * An imageapi image object returned by imageapi_image_open(). * @param $radius * The radius of the gaussian, in pixels, not counting the center pixel. (default 0.5) * @param $sigma * The standard deviation of the gaussian, in pixels. (default 0.5) * @param $amount * The percentage of the difference between the original and the blur image that is * added back into the original. (default 100) * @param $threshold * The threshold, as a fraction of max RGB levels, needed to apply the difference * amount. (default 0.05) * @return * True or FALSE, based on success. */ function imageapi_image_sharpen(&$image, $radius, $sigma, $amount, $threshold) { return imageapi_toolkit_invoke('sharpen', $image, array($radius, $sigma, $amount, $threshold)); } /** * Crop an image to the rectangle specified by the given rectangle. * * @param $image * An image object returned by imageapi_image_open(). * @param $x * The top left coordinate, in pixels, of the crop area (x axis value). * @param $y * The top left coordinate, in pixels, of the crop area (y axis value). * @param $width * The target width, in pixels. * @param $height * The target height, in pixels. * @return * TRUE or FALSE, based on success. */ function imageapi_image_crop(&$image, $x, $y, $width, $height) { $aspect = $image->info['height'] / $image->info['width']; if (empty($height)) $height = $width / $aspect; if (empty($width)) $width = $height * $aspect; $width = (int) round($width); $height = (int) round($height); return imageapi_toolkit_invoke('crop', $image, array($x, $y, $width, $height)); } /** * Convert an image to grayscale. * * @param $image * An image object returned by imageapi_image_open(). * @return * TRUE or FALSE, based on success. */ function imageapi_image_desaturate(&$image) { return imageapi_toolkit_invoke('desaturate', $image); } /** * Open an image file and return an image object. * * Any changes to the file are not saved until imageapi_image_close() is called. * * @param $file * Path to an image file. * @param $toolkit * An optional, image toolkit name to override the default. * @return * An image object or FALSE if there was a problem loading the file. The * image object has the following properties: * - 'source' - The original file path. * - 'info' - The array of information returned by image_get_info() * - 'toolkit' - The name of the image toolkit requested when the image was * loaded. * Image tookits may add additional properties. The caller is advised not to * monkey about with them. */ function imageapi_image_open($file, $toolkit = FALSE) { if (!$toolkit) { $toolkit = imageapi_default_toolkit(); } if ($toolkit) { $image = new stdClass(); $image->source = $file; $image->info = image_get_info($file); $image->toolkit = $toolkit; if (imageapi_toolkit_invoke('open', $image)) { return $image; } } return FALSE; } /** * Close the image and save the changes to a file. * * @param $image * An image object returned by imageapi_image_open(). The object's 'info' * property will be updated if the file is saved successfully. * @param $destination * Destination path where the image should be saved. If it is empty the * original image file will be overwritten. * @return * TRUE or FALSE, based on success. */ function imageapi_image_close($image, $destination = NULL) { if (empty($destination)) { $destination = $image->source; } if ($return = imageapi_toolkit_invoke('close', $image, array($destination))) { // Clear the cached file size and refresh the image information. clearstatcache(); $image->info = image_get_info($destination); if (@chmod($destination, 0664)) { return $return; } watchdog('imageapi', 'Could not set permissions on destination file: %file', array('%file' => $destination)); } return FALSE; } /** * Convert a hex string to its RGBA (Red, Green, Blue, Alpha) integer * components. * * @param $hex * A string specifing an RGB color in the formats: * '#ABC','ABC','#ABCD','ABCD','#AABBCC','AABBCC','#AABBCCDD','AABBCCDD' * @return * An array with four elements for red, green, blue, and alpha. */ function imageapi_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); $a = '0'; } elseif (preg_match('/^[0-9a-f]{6}$/i', $hex)) { // #FFAA33 or r=FF, g=AA, b=33 list($r, $g, $b) = str_split($hex, 2); $a = '0'; } elseif (preg_match('/^[0-9a-f]{8}$/i', $hex)) { // #FFAA33 or r=FF, g=AA, b=33 list($r, $g, $b, $a) = str_split($hex, 2); } 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); } else { //error: invalide hex string, TODO: set form error.. return FALSE; } $r = hexdec($r); $g = hexdec($g); $b = hexdec($b); $a = hexdec($a); return array($r, $g, $b, $a); }