Make WordPress Core

Changeset 33807


Ignore:
Timestamp:
08/30/2015 02:40:40 AM (11 years ago)
Author:
wonderboymusic
Message:

Improve the reliability of the crop returned by image_get_intermediate_size().

Add a bunch of unit tests to tests/image/intermediate_size.php.

Props joemcgill, ericlewis, kitchin, SergeyBiryukov, chipbennett.
Fixes #17626.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/media.php

    r33705 r33807  
    608608        // get the best one for a specified set of dimensions
    609609        if ( is_array($size) && !empty($imagedata['sizes']) ) {
    610                 $areas = array();
     610                $candidates = array();
    611611
    612612                foreach ( $imagedata['sizes'] as $_size => $data ) {
    613                         // already cropped to width or height; so use this size
    614                         if ( ( $data['width'] == $size[0] && $data['height'] <= $size[1] ) || ( $data['height'] == $size[1] && $data['width'] <= $size[0] ) ) {
     613                        // If there's an exact match to an existing image size, short circuit.
     614                        if ( $data['width'] == $size[0] && $data['height'] == $size[1] ) {
    615615                                $file = $data['file'];
    616616                                list($width, $height) = image_constrain_size_for_editor( $data['width'], $data['height'], $size );
    617617                                return compact( 'file', 'width', 'height' );
    618618                        }
    619                         // add to lookup table: area => size
    620                         $areas[$data['width'] * $data['height']] = $_size;
    621                 }
    622                 if ( !$size || !empty($areas) ) {
     619                        // If it's not an exact match but it's at least the dimensions requested.
     620                        if ( $data['width'] >= $size[0] && $data['height'] >= $size[1] ) {
     621                                $candidates[ $data['width'] * $data['height'] ] = $_size;
     622                        }
     623                }
     624
     625                if ( ! empty( $candidates ) ) {
    623626                        // find for the smallest image not smaller than the desired size
    624                         ksort($areas);
    625                         foreach ( $areas as $_size ) {
     627                        ksort( $candidates );
     628                        foreach ( $candidates as $_size ) {
    626629                                $data = $imagedata['sizes'][$_size];
    627                                 if ( $data['width'] >= $size[0] || $data['height'] >= $size[1] ) {
    628                                         // Skip images with unexpectedly divergent aspect ratios (crops)
    629                                         // First, we calculate what size the original image would be if constrained to a box the size of the current image in the loop
    630                                         $maybe_cropped = image_resize_dimensions($imagedata['width'], $imagedata['height'], $data['width'], $data['height'], false );
    631                                         // If the size doesn't match within one pixel, then it is of a different aspect ratio, so we skip it, unless it's the thumbnail size
    632                                         if ( 'thumbnail' != $_size && ( !$maybe_cropped || ( $maybe_cropped[4] != $data['width'] && $maybe_cropped[4] + 1 != $data['width'] ) || ( $maybe_cropped[5] != $data['height'] && $maybe_cropped[5] + 1 != $data['height'] ) ) )
    633                                                 continue;
    634                                         // If we're still here, then we're going to use this size
    635                                         $file = $data['file'];
    636                                         list($width, $height) = image_constrain_size_for_editor( $data['width'], $data['height'], $size );
    637                                         return compact( 'file', 'width', 'height' );
     630
     631                                // Skip images with unexpectedly divergent aspect ratios (crops)
     632                                // First, we calculate what size the original image would be if constrained to a box the size of the current image in the loop
     633                                $maybe_cropped = image_resize_dimensions($imagedata['width'], $imagedata['height'], $data['width'], $data['height'], false );
     634                                // If the size doesn't match within one pixel, then it is of a different aspect ratio, so we skip it, unless it's the thumbnail size
     635                                if ( 'thumbnail' != $_size &&
     636                                  ( ! $maybe_cropped
     637                                    || ( $maybe_cropped[4] != $data['width'] && $maybe_cropped[4] + 1 != $data['width'] )
     638                                    || ( $maybe_cropped[5] != $data['height'] && $maybe_cropped[5] + 1 != $data['height'] )
     639                                  ) ) {
     640                                  continue;
    638641                                }
     642                                // If we're still here, then we're going to use this size
     643                                $file = $data['file'];
     644                                list( $width, $height ) = image_constrain_size_for_editor( $data['width'], $data['height'], $size );
     645                                return compact( 'file', 'width', 'height' );
    639646                        }
    640647                }
  • trunk/tests/phpunit/tests/image/intermediate_size.php

    r29120 r33807  
    1111        }
    1212
     13        /**
     14         * Upload files and create attachements for testing
     15         */
     16        private function _make_attachment( $file, $parent_post_id = 0 ) {
     17                $contents = file_get_contents( $file );
     18                $upload = wp_upload_bits( basename( $file ), null, $contents );
     19
     20                $type = '';
     21                if ( ! empty( $upload['type'] ) ) {
     22                        $type = $upload['type'];
     23                } else {
     24                        $mime = wp_check_filetype( $upload['file'] );
     25                        if ( $mime ) {
     26                                $type = $mime['type'];
     27                        }
     28                }
     29
     30                $attachment = array(
     31                        'post_title' => basename( $upload['file'] ),
     32                        'post_content' => '',
     33                        'post_type' => 'attachment',
     34                        'post_parent' => $parent_post_id,
     35                        'post_mime_type' => $type,
     36                        'guid' => $upload['url'],
     37                );
     38
     39                // Save the data
     40                $id = wp_insert_attachment( $attachment, $upload[ 'file' ], $parent_post_id );
     41                wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );
     42
     43                $this->ids[] = $id;
     44                return $id;
     45        }
     46
    1347        function test_make_intermediate_size_no_size() {
    1448                $image = image_make_intermediate_size( DIR_TESTDATA . '/images/a2-small.jpg', 0, 0, false );
     
    5084                unlink( DIR_TESTDATA . '/images/a2-small-100x75.jpg' );
    5185        }
     86
     87        /**
     88        * @ticket 17626
     89        */
     90        function test_get_intermediate_sizes_by_name() {
     91                add_image_size( 'test-size', 330, 220, true );
     92
     93                $file = DIR_TESTDATA . '/images/waffles.jpg';
     94                $id = $this->_make_attachment( $file, 0 );
     95
     96                // look for a size by name
     97                $image = image_get_intermediate_size( $id, 'test-size' );
     98
     99                // test for the expected string because the array will by definition
     100                // return with the correct height and width attributes
     101                $this->assertNotFalse( strpos( $image['file'], '330x220' ) );
     102
     103                // cleanup
     104                remove_image_size( 'test-size' );
     105        }
     106
     107        /**
     108        * @ticket 17626
     109        */
     110        function test_get_intermediate_sizes_by_array_exact() {
     111                // Only one dimention match shouldn't return false positive (see: 17626)
     112                add_image_size( 'test-size', 330, 220, true );
     113                add_image_size( 'false-height', 330, 400, true );
     114                add_image_size( 'false-width', 600, 220, true );
     115
     116                $file = DIR_TESTDATA . '/images/waffles.jpg';
     117                $id = $this->_make_attachment( $file, 0 );
     118
     119                // look for a size by array that exists
     120                // note: staying larger than 300px to miss default medium crop
     121                $image = image_get_intermediate_size( $id, array( 330, 220 ) );
     122
     123                // test for the expected string because the array will by definition
     124                // return with the correct height and width attributes
     125                $this->assertNotFalse( strpos( $image['file'], '330x220' ) );
     126
     127                // cleanup
     128                remove_image_size( 'test-size' );
     129                remove_image_size( 'false-height' );
     130                remove_image_size( 'false-width' );
     131        }
     132
     133        /**
     134        * @ticket 17626
     135        */
     136        function test_get_intermediate_sizes_by_array_nearest() {
     137                // If an exact size is not found, it should be returned
     138                // If not, find nearest size that is larger (see: 17626)
     139                add_image_size( 'test-size', 450, 300, true );
     140                add_image_size( 'false-height', 330, 100, true );
     141                add_image_size( 'false-width', 150, 220, true );
     142
     143                $file = DIR_TESTDATA . '/images/waffles.jpg';
     144                $id = $this->_make_attachment( $file, 0 );
     145
     146                // look for a size by array that doesn't exist
     147                // note: staying larger than 300px to miss default medium crop
     148                $image = image_get_intermediate_size( $id, array( 330, 220 ) );
     149
     150                // you have to test for the string because the image will by definition
     151                // return with the correct height and width attributes
     152                $this->assertNotFalse( strpos( $image['file'], '450x300' ) );
     153
     154                // cleanup
     155                remove_image_size( 'test-size' );
     156                remove_image_size( 'false-height' );
     157                remove_image_size( 'false-width' );
     158        }
     159
     160        /**
     161        * @ticket 17626
     162        */
     163        function test_get_intermediate_sizes_by_array_nearest_false() {
     164                // If an exact size is not found, it should be returned
     165                // If not, find nearest size that is larger, otherwise return false (see: 17626)
     166                add_image_size( 'false-height', 330, 100, true );
     167                add_image_size( 'false-width', 150, 220, true );
     168
     169                $file = DIR_TESTDATA . '/images/waffles.jpg';
     170                $id = $this->_make_attachment( $file, 0 );
     171
     172                // look for a size by array that doesn't exist
     173                // note: staying larger than 300px to miss default medium crop
     174                $image = image_get_intermediate_size( $id, array( 330, 220 ) );
     175
     176                // you have to test for the string because the image will by definition
     177                // return with the correct height and width attributes
     178                $this->assertFalse( $image );
     179
     180                // cleanup
     181                remove_image_size( 'false-height' );
     182                remove_image_size( 'false-width' );
     183        }
     184
     185        /**
     186        * @ticket 17626
     187        */
     188        function test_get_intermediate_sizes_by_array_zero_height() {
     189                // Generate random width
     190                $random_w = rand( 300, 400 );
     191
     192                // Only one dimention match shouldn't return false positive (see: 17626)
     193                add_image_size( 'test-size', $random_w, 0, false );
     194                add_image_size( 'false-height', $random_w, 100, true );
     195
     196                $file = DIR_TESTDATA . '/images/waffles.jpg';
     197                $id = $this->_make_attachment( $file, 0 );
     198
     199                $original = wp_get_attachment_metadata( $id );
     200                $image_w = $random_w;
     201                $image_h = round( ( $image_w / $original['width'] ) * $original['height'] );
     202
     203                // look for a size by array that exists
     204                // note: staying larger than 300px to miss default medium crop
     205                $image = image_get_intermediate_size( $id, array( $random_w, 0 ) );
     206
     207                // test for the expected string because the array will by definition
     208                // return with the correct height and width attributes
     209                $this->assertNotFalse( strpos( $image['file'], $image_w . 'x' . $image_h ) );
     210
     211                // cleanup
     212                remove_image_size( 'test-size' );
     213                remove_image_size( 'false-height' );
     214        }
     215
     216        /**
     217        * @ticket 17626
     218        */
     219        function test_get_intermediate_sizes_by_array_zero_width() {
     220                // Generate random height
     221                $random_h = rand( 200, 300 );
     222
     223                // Only one dimention match shouldn't return false positive (see: 17626)
     224                add_image_size( 'test-size', 0, $random_h, false );
     225                add_image_size( 'false-height', 300, $random_h, true );
     226
     227                $file = DIR_TESTDATA . '/images/waffles.jpg';
     228                $id = $this->_make_attachment( $file, 0 );
     229
     230                $original = wp_get_attachment_metadata( $id );
     231                $image_h = $random_h;
     232                $image_w = round( ( $image_h / $original['height'] ) * $original['width'] );
     233
     234                // look for a size by array that exists
     235                // note: staying larger than 300px to miss default medium crop
     236                $image = image_get_intermediate_size( $id, array( 0, $random_h ) );
     237
     238                // test for the expected string because the array will by definition
     239                // return with the correct height and width attributes
     240                $this->assertNotFalse( strpos( $image['file'], $image_w . 'x' . $image_h ) );
     241
     242                // cleanup
     243                remove_image_size( 'test-size' );
     244                remove_image_size( 'false-height' );
     245        }
    52246}
Note: See TracChangeset for help on using the changeset viewer.