Skip to main content

Cropping Post Thumbnails from Top instead of Center in WordPress

Last week someone I know asked me how to do always a top center crop on thumbnails and I knew it was possible because of WP_Image_Editor. But you always wondered what it was before 3.5 and it always seemed you needed to hack code till I founded a post from George Stephanis. His post shows that in 3.4 you where able to do that with the filter ‘image_resize_dimensions’. But if you read the last code example you would see it would apply it to all sizes. Maybe there are ways to use globals/filters to request the size but it ain’t easy.

In 3.5 that code didn’t changed and maybe it should since putting the logic inside WP_Image_Editor means you must know what you are doing. This is because other plugins can also add their implementations and can overwrite the logic for changing the crop position. Probably a filter inside wp_get_image_editor() makes sense to have or outside if the context is import to know.

Understanding the code

The method that handles all sizes inside a WP_Image_Editor is multi_resize(). It receives an array with all the sizes as keys and each containing an array with the width, height and crop variable. The following code example I copy/paste the function wp_generate_attachment_metadata(). As you see it builds a sizes array and passes it to multi_resize(). That will return an array with all the image metadata.

function wp_generate_attachment_metadata( $attachment_id, $file ) {
	$attachment = get_post( $attachment_id );

	$metadata = array();
	if ( preg_match('!^image/!', get_post_mime_type( $attachment )) && file_is_displayable_image($file) ) {
		$imagesize = getimagesize( $file );
		$metadata['width'] = $imagesize[0];
		$metadata['height'] = $imagesize[1];

		// Make the file path relative to the upload dir
		$metadata['file'] = _wp_relative_upload_path($file);

		// make thumbnails and other intermediate sizes
		global $_wp_additional_image_sizes;

		foreach ( get_intermediate_image_sizes() as $s ) {
			$sizes[$s] = array( 'width' => '', 'height' => '', 'crop' => false );
			if ( isset( $_wp_additional_image_sizes[$s]['width'] ) )
				$sizes[$s]['width'] = intval( $_wp_additional_image_sizes[$s]['width'] ); // For theme-added sizes
				$sizes[$s]['width'] = get_option( "{$s}_size_w" ); // For default sizes set in options
			if ( isset( $_wp_additional_image_sizes[$s]['height'] ) )
				$sizes[$s]['height'] = intval( $_wp_additional_image_sizes[$s]['height'] ); // For theme-added sizes
				$sizes[$s]['height'] = get_option( "{$s}_size_h" ); // For default sizes set in options
			if ( isset( $_wp_additional_image_sizes[$s]['crop'] ) )
				$sizes[$s]['crop'] = intval( $_wp_additional_image_sizes[$s]['crop'] ); // For theme-added sizes
				$sizes[$s]['crop'] = get_option( "{$s}_crop" ); // For default sizes set in options

		$sizes = apply_filters( 'intermediate_image_sizes_advanced', $sizes );

		if ( $sizes ) {
			$editor = wp_get_image_editor( $file );

			if ( ! is_wp_error( $editor ) )
				$metadata['sizes'] = $editor->multi_resize( $sizes );
		} else {
			$metadata['sizes'] = array();

		// fetch additional metadata from exif/iptc
		$image_meta = wp_read_image_metadata( $file );
		if ( $image_meta )
			$metadata['image_meta'] = $image_meta;


	return apply_filters( 'wp_generate_attachment_metadata', $metadata, $attachment_id );


public function multi_resize( $sizes ) {
	$metadata = array();
	$orig_size = $this->size;

	foreach ( $sizes as $size => $size_data ) {
		$image = $this->_resize( $size_data['width'], $size_data['height'], $size_data['crop'] );

		if( ! is_wp_error( $image ) ) {
			$resized = $this->_save( $image );

			imagedestroy( $image );

			if ( ! is_wp_error( $resized ) && $resized ) {
				unset( $resized['path'] );
				$metadata[$size] = $resized;

		$this->size = $orig_size;

	return $metadata;

So above you see how multi_resize() works. For our purpose we only need to make and if/else on calling _resize(). We do need something like the method _resize() but then with our own coordinates.  And our customized _resize() would overwrite the variable $src_y to the value of 0. Most likely in a real world example you would integrate all crop positions to the new created method so you can easily switch.

End result

In case you wondered why two files are needed, please read the post about how to include a custom image editor

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.