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
			else
				$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
			else
				$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
			else
				$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 );
}

multi_resize()

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

https://gist.github.com/markoheijnen/5134202#file-unknown-crop-top-center.php

https://gist.github.com/markoheijnen/5134202#file-editor-gd.php

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.