diff --git a/core/includes/image.inc b/core/includes/image.inc index f6ae7f1..d7668fc 100644 --- a/core/includes/image.inc +++ b/core/includes/image.inc @@ -178,86 +178,132 @@ function image_scale_and_crop(stdClass $image, $width, $height) { } /** - * Scales image dimensions while maintaining aspect ratio. - * - * The resulting dimensions can be smaller for one or both target dimensions. - * - * @param $dimensions - * Dimensions to be modified - an array with components width and height, in - * pixels. - * @param $width - * The target width, in pixels. If this value is NULL then the scaling will be - * based only on the height value. - * @param $height - * The target height, in pixels. If this value is NULL then the scaling will - * be based only on the width value. - * @param $upscale - * Boolean indicating that images smaller than the target dimensions will be - * scaled up. This generally results in a low quality image. - * - * @return - * TRUE if $dimensions was modified, FALSE otherwise. + * Image dimensions callback; Scale. + * + * @param array $dimensions + * Dimensions to be modified - an array with components width and height in + * pixels (int), or 0 or null if unknown. + * @param array $data + * An array of attributes to use when performing the scale effect with the + * following items: + * - "width": An integer representing the desired width in pixels, or 0 or + * null if the scaling should only be based on the height value. + * - "height": An integer representing the desired height in pixels, or 0 or + * null if the scaling should only be based on the height value. + * - "upscale": A boolean indicating that the image should be upscaled if the + * dimensions are larger than the original image. * * @see image_scale() */ -function image_dimensions_scale(array &$dimensions, $width = NULL, $height = NULL, $upscale = FALSE) { - $aspect = $dimensions['height'] / $dimensions['width']; - - // Calculate one of the dimensions from the other target dimension, - // ensuring the same aspect ratio as the source dimensions. If one of the - // target dimensions is missing, that is the one that is calculated. If both - // are specified then the dimension calculated is the one that would not be - // calculated to be bigger than its target. - if (($width && !$height) || ($width && $height && $aspect < $height / $width)) { - $height = (int) round($width * $aspect); +function image_scale_dimensions(array &$dimensions, array $data) { + $width = $data['width']; + $height = $data['height']; + $upscale = $data['upscale']; + + // We can only calculate the new dimensions if we know the current dimensions. + if ($dimensions['width'] > 0 && $dimensions['height'] > 0) { + if (!empty($width) && !empty($height)) { + // Scale within bounding box. + $scale_factor_x = $width / $dimensions['width']; + $scale_factor_y = $height / $dimensions['height']; + if ($scale_factor_x <= $scale_factor_y) { + // The width is more restricting, use that factor. + $scale_factor = $scale_factor_x; + $height = $scale_factor * $dimensions['height']; + } + else { + // The height is more restricting, use that factor. + $scale_factor = $scale_factor_y; + $width = $scale_factor * $dimensions['width']; + } + } + else if (!empty($width)) { + // Scale to given width. + $scale_factor = $width / $dimensions['width']; + $height = $scale_factor * $dimensions['height']; + } + else if (!empty($height)) { + // Scale to given height. + $scale_factor = $height / $dimensions['height']; + $width = $scale_factor * $dimensions['width']; + } + else { + // No new dimensions passed in, so we cannot scale. + $scale_factor = NULL; + $dimensions['width'] = NULL; + $dimensions['height'] = NULL; + } + // The scale effect will only be executed if we are scaling down or if + // $upscale is set. Otherwise the dimensions remain unchanged. + if ($upscale || (isset($scale_factor) && $scale_factor <= 1)) { + $dimensions['width'] = $width; + $dimensions['height'] = $height; + } } else { - $width = (int) round($height / $aspect); - } - - // Don't upscale if the option isn't enabled. - if (!$upscale && ($width >= $dimensions['width'] || $height >= $dimensions['height'])) { - return FALSE; + // Set the new dimensions to null. + $dimensions['width'] = NULL; + $dimensions['height'] = NULL; + // However, we might know one of the new dimensions. + if (!empty($width) && empty($height)) { + // We might know the width if we are scaling unconditionally ($upscale is + // set), or if the new width is smaller then the current width. For the + // latter the current width must be known to be able to determine this: + // we check by testing if a convert to int gives a positive result. + if ($upscale || $width <= (int) $dimensions['width']) { + $dimensions['width'] = $width; + } + } + else if (empty($width) && !empty($height)) { + // See comment about width a few lines above. + if ($upscale || $height <= (int) $dimensions['height']) { + $dimensions['height'] = $height; + } + } } - - $dimensions['width'] = $width; - $dimensions['height'] = $height; - return TRUE; } /** * Scales an image while maintaining aspect ratio. * - * The resulting image can be smaller for one or both target dimensions. + * If only one of the $width and $height parameters is specified, the image is + * scaled such that the resulting width or height equals the given width or + * height. If both width and height are specified, the image is scaled (while + * maintaining aspect ratio) within the bounding box defined by the given width + * and height. * - * @param $image + * @param object $image * An image object returned by image_load(). - * @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 + * @param int|null $width + * The target width, in pixels. If this value is 0 or null, then the scaling + * will be based only on the height value. + * @param int|null $height + * The target height, in pixels. If this value is 0 or null, then the scaling + * will be based only on the width value. + * @param bool $upscale * Boolean indicating that files smaller than the dimensions will be scaled * up. This generally results in a low quality image. * - * @return - * TRUE on success, FALSE on failure. + * @return bool + * true on success, false on failure. * * @see image_dimensions_scale() * @see image_load() * @see image_scale_and_crop() */ -function image_scale(stdClass $image, $width = NULL, $height = NULL, $upscale = FALSE) { - $dimensions = $image->info; +function image_scale(stdClass $image, $width, $height, $upscale = FALSE) { + $width = (int) round($width); + $height = (int) round($height); - // Scale the dimensions - if they don't change then just return success. - if (!image_dimensions_scale($dimensions, $width, $height, $upscale)) { - return TRUE; + if (empty($width) && empty($height)) { + // We won't be able to scale if none of the dimensions are known. + return FALSE; } - return image_resize($image, $dimensions['width'], $dimensions['height']); + // We defer any calculations to the toolkit specific implementation. This + // because we can't be sure that both the current width and the current height + // are known, nor that they are needed for the current toolkit. + return image_toolkit_invoke('scale', $image, array($width, $height, $upscale)); } /** diff --git a/core/modules/image/image.effects.inc b/core/modules/image/image.effects.inc index 35a6a74..1b1c2f2 100644 --- a/core/modules/image/image.effects.inc +++ b/core/modules/image/image.effects.inc @@ -137,26 +137,6 @@ function image_scale_effect(&$image, $data) { } /** - * Image dimensions callback; Scale. - * - * @param $dimensions - * Dimensions to be modified - an array with components width and height, in - * pixels. - * @param $data - * An array of attributes to use when performing the scale effect with the - * following items: - * - "width": An integer representing the desired width in pixels. - * - "height": An integer representing the desired height in pixels. - * - "upscale": A boolean indicating that the image should be upscaled if the - * dimensions are larger than the original image. - */ -function image_scale_dimensions(array &$dimensions, array $data) { - if ($dimensions['width'] && $dimensions['height']) { - image_dimensions_scale($dimensions, $data['width'], $data['height'], $data['upscale']); - } -} - -/** * Image effect callback; Crop an image resource. * * @param $image diff --git a/core/modules/image/image.test b/core/modules/image/image.test index 2c422a7..212e75e 100644 --- a/core/modules/image/image.test +++ b/core/modules/image/image.test @@ -262,14 +262,14 @@ class ImageEffectsUnitTest extends ImageToolkitTestCase { * Test the image_scale_effect() function. */ function testScaleEffect() { - // @todo: need to test upscaling. - $this->assertTrue(image_scale_effect($this->image, array('width' => 10, 'height' => 10)), t('Function returned the expected value.')); - $this->assertToolkitOperationsCalled(array('resize')); + $this->assertTrue(image_scale_effect($this->image, array('width' => 10, 'height' => 20, 'upscale' => TRUE)), t('Function returned the expected value.')); + $this->assertToolkitOperationsCalled(array('scale')); // Check the parameters. $calls = image_test_get_all_calls(); - $this->assertEqual($calls['resize'][0][1], 10, t('Width was passed correctly')); - $this->assertEqual($calls['resize'][0][2], 5, t('Height was based off aspect ratio and passed correctly')); + $this->assertEqual($calls['scale'][0][1], 10, t('Width was passed correctly')); + $this->assertEqual($calls['scale'][0][2], 20, t('Height was passed correctly')); + $this->assertEqual($calls['scale'][0][3], TRUE, t('Upscale was passed correctly')); } /** @@ -934,7 +934,7 @@ class ImageDimensionsUnitTest extends DrupalWebTestCase { image_effect_save('test', $effect); $img_tag = theme_image_style($variables); - $this->assertEqual($img_tag, ''); + $this->assertTrue(strpos($img_tag, 'width="120"') !== FALSE && strpos($img_tag, 'height="60"') !== FALSE, t('Image tag contains width and height attributes with correct values.')); $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.')); $this->drupalGet($url); $this->assertResponse(200, t('Image was generated at the URL.')); @@ -955,7 +955,7 @@ class ImageDimensionsUnitTest extends DrupalWebTestCase { image_effect_save('test', $effect); $img_tag = theme_image_style($variables); - $this->assertEqual($img_tag, ''); + $this->assertTrue(strpos($img_tag, 'width="60"') !== FALSE && strpos($img_tag, 'height="120"') !== FALSE, t('Image tag contains width and height attributes with correct values.')); $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.')); $this->drupalGet($url); $this->assertResponse(200, t('Image was generated at the URL.')); @@ -977,7 +977,7 @@ class ImageDimensionsUnitTest extends DrupalWebTestCase { image_effect_save('test', $effect); $img_tag = theme_image_style($variables); - $this->assertEqual($img_tag, ''); + $this->assertTrue(strpos($img_tag, 'width="45"') !== FALSE && strpos($img_tag, 'height="90"') !== FALSE, t('Image tag contains width and height attributes with correct values.')); $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.')); $this->drupalGet($url); $this->assertResponse(200, t('Image was generated at the URL.')); @@ -999,7 +999,7 @@ class ImageDimensionsUnitTest extends DrupalWebTestCase { image_effect_save('test', $effect); $img_tag = theme_image_style($variables); - $this->assertEqual($img_tag, ''); + $this->assertTrue(strpos($img_tag, 'width="45"') !== FALSE && strpos($img_tag, 'height="90"') !== FALSE, t('Image tag contains width and height attributes with correct values.')); $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.')); $this->drupalGet($url); $this->assertResponse(200, t('Image was generated at the URL.')); @@ -1017,7 +1017,7 @@ class ImageDimensionsUnitTest extends DrupalWebTestCase { image_effect_save('test', $effect); $img_tag = theme_image_style($variables); - $this->assertEqual($img_tag, ''); + $this->assertTrue(strpos($img_tag, 'width="45"') !== FALSE && strpos($img_tag, 'height="90"') !== FALSE, t('Image tag contains width and height attributes with correct values.')); $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.')); $this->drupalGet($url); $this->assertResponse(200, t('Image was generated at the URL.')); @@ -1038,7 +1038,7 @@ class ImageDimensionsUnitTest extends DrupalWebTestCase { image_effect_save('test', $effect); $img_tag = theme_image_style($variables); - $this->assertEqual($img_tag, ''); + $this->assertTrue(strpos($img_tag, 'width="') === FALSE && strpos($img_tag, 'height="') === FALSE, t('Image tag does not contain width and height attributes.')); $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.')); $this->drupalGet($url); $this->assertResponse(200, t('Image was generated at the URL.')); @@ -1058,7 +1058,7 @@ class ImageDimensionsUnitTest extends DrupalWebTestCase { image_effect_save('test', $effect); $img_tag = theme_image_style($variables); - $this->assertEqual($img_tag, ''); + $this->assertTrue(strpos($img_tag, 'width="30"') !== FALSE && strpos($img_tag, 'height="30"') !== FALSE, t('Image tag contains width and height attributes with correct values.')); $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.')); $this->drupalGet($url); $this->assertResponse(200, t('Image was generated at the URL.')); @@ -1079,7 +1079,7 @@ class ImageDimensionsUnitTest extends DrupalWebTestCase { $effect = image_effect_save('test', $effect); $img_tag = theme_image_style($variables); - $this->assertEqual($img_tag, ''); + $this->assertTrue(strpos($img_tag, 'width="') === FALSE && strpos($img_tag, 'height="') === FALSE, t('Image tag does not contain width and height attributes.')); $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.')); $this->drupalGet($url); $this->assertResponse(200, t('Image was generated at the URL.')); @@ -1097,26 +1097,26 @@ class ImageDimensionsUnitTest extends DrupalWebTestCase { image_effect_save('test', $effect); $img_tag = theme_image_style($variables); - $this->assertEqual($img_tag, ''); + $this->assertTrue(strpos($img_tag, 'width="') === FALSE && strpos($img_tag, 'height="') === FALSE, t('Image tag does not contain width and height attributes.')); } } /** - * Tests image_dimensions_scale(). + * Tests image_scale_dimensions(). */ -class ImageDimensionsScaleTestCase extends DrupalUnitTestCase { +class ImageScaleDimensionsTestCase extends DrupalUnitTestCase { public static function getInfo() { return array( - 'name' => 'image_dimensions_scale()', - 'description' => 'Tests all control flow branches in image_dimensions_scale().', + 'name' => 'image_scale_dimensions()', + 'description' => 'Tests all control flow branches in image_scale_dimensions().', 'group' => 'Image', ); } /** - * Tests all control flow branches in image_dimensions_scale(). + * Tests all control flow branches in image_scale_dimensions(). */ - function testImageDimensionsScale() { + function testImagescaleDimensions() { // Define input / output datasets to test different branch conditions. $test = array(); @@ -1129,16 +1129,17 @@ class ImageDimensionsScaleTestCase extends DrupalUnitTestCase { 'width' => 1000, 'height' => 2000, ), - 'width' => 200, - 'height' => NULL, - 'upscale' => TRUE, + 'data' => array( + 'width' => 200, + 'height' => NULL, + 'upscale' => TRUE, + ), ), 'output' => array( 'dimensions' => array( 'width' => 200, 'height' => 400, ), - 'return_value' => TRUE, ), ); @@ -1151,16 +1152,17 @@ class ImageDimensionsScaleTestCase extends DrupalUnitTestCase { 'width' => 1000, 'height' => 800, ), - 'width' => NULL, - 'height' => 140, - 'upscale' => FALSE, + 'data' => array( + 'width' => NULL, + 'height' => 140, + 'upscale' => FALSE, + ), ), 'output' => array( 'dimensions' => array( 'width' => 175, 'height' => 140, ), - 'return_value' => TRUE, ), ); @@ -1173,16 +1175,17 @@ class ImageDimensionsScaleTestCase extends DrupalUnitTestCase { 'width' => 8, 'height' => 20, ), - 'width' => 200, - 'height' => 140, - 'upscale' => TRUE, + 'data' => array( + 'width' => 200, + 'height' => 140, + 'upscale' => TRUE, + ), ), 'output' => array( 'dimensions' => array( 'width' => 56, 'height' => 140, ), - 'return_value' => TRUE, ), ); @@ -1193,16 +1196,17 @@ class ImageDimensionsScaleTestCase extends DrupalUnitTestCase { 'width' => 2000, 'height' => 800, ), - 'width' => 200, - 'height' => 140, - 'upscale' => FALSE, + 'data' => array( + 'width' => 200, + 'height' => 140, + 'upscale' => FALSE, + ), ), 'output' => array( 'dimensions' => array( 'width' => 200, 'height' => 80, ), - 'return_value' => TRUE, ), ); @@ -1213,31 +1217,29 @@ class ImageDimensionsScaleTestCase extends DrupalUnitTestCase { 'width' => 100, 'height' => 50, ), - 'width' => 200, - 'height' => 140, - 'upscale' => FALSE, + 'data' => array( + 'width' => 200, + 'height' => 140, + 'upscale' => FALSE, + ), ), 'output' => array( 'dimensions' => array( 'width' => 100, 'height' => 50, ), - 'return_value' => FALSE, ), ); foreach ($tests as $test) { // Process the test dataset. - $return_value = image_dimensions_scale($test['input']['dimensions'], $test['input']['width'], $test['input']['height'], $test['input']['upscale']); + image_scale_dimensions($test['input']['dimensions'], $test['input']['data']); // Check the width. $this->assertEqual($test['output']['dimensions']['width'], $test['input']['dimensions']['width'], t('Computed width (@computed_width) equals expected width (@expected_width)', array('@computed_width' => $test['output']['dimensions']['width'], '@expected_width' => $test['input']['dimensions']['width']))); // Check the height. $this->assertEqual($test['output']['dimensions']['height'], $test['input']['dimensions']['height'], t('Computed height (@computed_height) equals expected height (@expected_height)', array('@computed_height' => $test['output']['dimensions']['height'], '@expected_height' => $test['input']['dimensions']['height']))); - - // Check the return value. - $this->assertEqual($test['output']['return_value'], $return_value, t('Correct return value.')); } } } diff --git a/core/modules/simpletest/tests/image.test b/core/modules/simpletest/tests/image.test index deead57..e7ed5cf 100644 --- a/core/modules/simpletest/tests/image.test +++ b/core/modules/simpletest/tests/image.test @@ -124,14 +124,14 @@ class ImageToolkitUnitTest extends ImageToolkitTestCase { * Test the image_scale() function. */ function testScale() { -// TODO: need to test upscaling - $this->assertTrue(image_scale($this->image, 10, 10), t('Function returned the expected value.')); - $this->assertToolkitOperationsCalled(array('resize')); + $this->assertTrue(image_scale($this->image, 10, 20, TRUE), t('Function returned the expected value.')); + $this->assertToolkitOperationsCalled(array('scale')); // Check the parameters. $calls = image_test_get_all_calls(); - $this->assertEqual($calls['resize'][0][1], 10, t('Width was passed correctly')); - $this->assertEqual($calls['resize'][0][2], 5, t('Height was based off aspect ratio and passed correctly')); + $this->assertEqual($calls['scale'][0][1], 10, t('Width was passed correctly')); + $this->assertEqual($calls['scale'][0][2], 20, t('Height was passed correctly')); + $this->assertEqual($calls['scale'][0][3], TRUE, t('Upscale was passed correctly')); } /** diff --git a/core/modules/simpletest/tests/image_test.module b/core/modules/simpletest/tests/image_test.module index de640f0..7ddd69e 100644 --- a/core/modules/simpletest/tests/image_test.module +++ b/core/modules/simpletest/tests/image_test.module @@ -33,6 +33,7 @@ function image_test_reset() { 'save' => array(), 'settings' => array(), 'resize' => array(), + 'scale' => array(), 'rotate' => array(), 'crop' => array(), 'desaturate' => array(), @@ -46,8 +47,8 @@ function image_test_reset() { * * @return * An array keyed by operation name ('load', 'save', 'settings', 'resize', - * 'rotate', 'crop', 'desaturate') with values being arrays of parameters - * passed to each call. + * 'scale', 'rotate', 'crop', 'desaturate') with values being arrays of + * parameters passed to each call. */ function image_test_get_all_calls() { return variable_get('image_test_results', array()); @@ -58,7 +59,7 @@ function image_test_get_all_calls() { * * @param $op * One of the image toolkit operations: 'get_info', 'load', 'save', - * 'settings', 'resize', 'rotate', 'crop', 'desaturate'. + * 'settings', 'resize', 'scale', 'rotate', 'crop', 'desaturate'. * @param $args * Values passed to hook. * @@ -122,6 +123,14 @@ function image_test_resize(stdClass $image, $width, $height) { } /** + * Image tookit's resize operation. + */ +function image_test_scale(stdClass $image, $width, $height, $upscale) { + _image_test_log_call('scale', array($image, $width, $height, $upscale)); + return TRUE; +} + +/** * Image tookit's rotate operation. */ function image_test_rotate(stdClass $image, $degrees, $background = NULL) { diff --git a/core/modules/system/image.gd.inc b/core/modules/system/image.gd.inc index 39f86dc..551b90c 100644 --- a/core/modules/system/image.gd.inc +++ b/core/modules/system/image.gd.inc @@ -96,6 +96,46 @@ function image_gd_resize(stdClass $image, $width, $height) { } /** + * Implements the image scale effect for the GD toolkit. + * + * See the documentation of image_scale() for more details. + * + * @param object $image + * An image object returned by image_load(). + * @param int|null $width + * The target width, in pixels. If this value is null, then the scaling will + * be based only on the height value. + * @param int|null $height + * The target height, in pixels. If this value is null, then the scaling will + * be based only on the width value. + * @param bool $upscale + * Boolean indicating that files smaller than the dimensions will be scaled + * up. This generally results in a low quality image. + * + * @return bool + * true on success, false on failure. + * + * @see image_scale() + * @see image_scale_dimensions() + */ +function image_gd_scale(stdClass $image, $width, $height, $upscale) { + // GD does not natively support a scale operation. So we calculate the new + // dimension ourselves and then resize the image. + // We use the image_scale_dimensions() function for the calculation. + $dimensions = array( + 'width' => $image->info['width'], + 'height' => $image->info['height'] + ); + image_scale_dimensions($dimensions, array('width' => $width, 'height' => $height, 'upscale' => $upscale)); + + if ($dimensions['width'] !== $image->info['width'] || $dimensions['height'] !== $image->info['height']) { + // Dimensions will change, let resize do the real job. + return image_gd_resize($image, $dimensions['width'], $dimensions['height']); + } + return TRUE; +} + +/** * Rotate an image the given number of degrees. * * @param $image