Image Upload Adjustments

When uploading images, WordPress’ default behavior is to skip any defined image size if the uploaded image is too small to fill it. If the image’s width or height (but not both) is big enough, WP shrinks to fit within the rectangle but does not fill it. When both the width and height are big enough, WP always center-crops to fit.

Your theme layout may require a cropped image that fits, and fills, a certain rectangle. You may tell your client always to make sure images are of a certain minimum size, but I guarantee your advice will be forgotten. Here’s a function that enlarges the uploaded image if needed, and crops it to fill a particular named image size. Enlarging may result in a blurred image, but at least your layout won’t be broken.

As a bonus, this snippet comes from a fashion website, where it was assumed a ‘tall’ image (‘portrait’ orientation) was practically always an image of a person. WP’s default center-cropping tended to cut heads off, so this function always generates a newly-cropped version of the image if the source was ‘tall,’ cropped at the top instead of the center.

This example assumes two named image sizes, ‘feat_small’ and ‘feat_medium’ have been defined elsewhere in the function.php or similar sequence, and both these must be filled and cropped:

	add_theme_support( 'post-thumbnails' );
	add_image_size( 'feat_small', 250, 170, true );
	add_image_size( 'feat_medium', 352, 270, true );

Here’s the function, which hooks into wp_generate_attachment_metadata, after the upload has occurred and the image sizes have been written. See comments for detailed explanations:

function wpcx_featimage_adjust( $metadata, $aid ) {
	// Bail if something unexpected happens:
	if ( ! is_array( $metadata ) ) return $metadata;

	// Define / globalize certain variables
	global $_wp_additional_image_sizes;
	$updir = wp_upload_dir( );

	// $w1, $h1 are original source width, height. If for some reason $metadata is incomplete, bail:
	$w1 = $metadata['width'];
	$h1 = $metadata['height'];
	if ( ( int ) $w1 < 1 ) return $metadata;
	if ( ( int ) $h1 < 1 ) return $metadata;
	// Loop through the two named image sizes; these s/b array keys in $_wp_additional_image_sizes
	foreach ( array( 'feat_medium', 'feat_small' ) as $isize ):
		// Later we'll call wp_crop_image, which requires many args:
		// source x, y, source width/height, destination width/height, source file and dest. file.
		// If for some reason the input data is incomplete, move to the next.
		$destw = $_wp_additional_image_sizes[$isize]['width'];
		$desth = $_wp_additional_image_sizes[$isize]['height'];
		if ( ! $metadata['file'] || ! $destw || ! $desth ) { continue; }
		
		// calculate $sw, $sh = source width, height. Always less than or equal to original counterparts.
		$sw = $w1;
		$sh = $h1;
		if ( $w1 < $h1 ) { 	// source is tall ( top crop )
			$x1 = $y1 = 0;
			$ch = $w1 * $desth / $destw;
			if ( $ch < $h1 ) {
				$sh = $ch;
			}
		} else { 		// source is wide or square ( center crop )
			// if image is larger than destination WxH then WP has already center-cropped; move on.
			if ( $metadata['sizes'] &&
					$metadata['sizes'][$isize] &&
					$metadata['sizes'][$isize]['width'] >= $destw &&
					$metadata['sizes'][$isize]['height'] >= $desth ) continue;
			$y1 = 0;
			$cw = $h1 * $destw/$desth;
			if ( $cw < $w1 ) {
				$x1 = floor( ( $w1 - $cw ) / 2 );
				$sw = $cw;
			} else {
				$x1 = 0;
			}
		}
		
		// numeric values calculated, now generate filenames
		$cropnamea = explode( '.', $metadata['file'] );
		$cropext = array_pop( $cropnamea );
		$croppath = trailingslashit( $updir['basedir'] ) . implode( '.', $cropnamea ) . '-' . $destw . 'x' . $desth . '.' . $cropext;
		$midpath = explode( '/', $metadata['file'] );
		array_pop( $midpath );
		$midpath = implode( '/', $midpath );
		
		$cropped = wp_crop_image( trailingslashit( $updir['basedir'] ) . $metadata['file'], $x1, $y1, $sw, $sh, $destw, $desth, false, $croppath );
		// move on if for some reason the crop didn't work
		if ( ! $cropped || is_wp_error( $cropped ) ) {
			continue;
		}
	
		// delete the previously-cropped version if it exists:
		if ( is_file( trailingslashit( $updir['basedir'] ) . trailingslashit( $midpath ) . $metadata['sizes'][$isize]['file'] ) )
			unlink( trailingslashit( $updir['basedir'] ) . trailingslashit( $midpath ) . $metadata['sizes'][$isize]['file'] );
	
		// update the metadata
		$metadata['sizes'][$isize] = array( 
			'file' => array_pop( explode( '/', $cropped ) ), 
			'width' => $destw, 
			'height' => $desth
		 );
	endforeach;
	return $metadata;

}
add_filter( 'wp_generate_attachment_metadata', 'wpcx_featimage_adjust', 15, 2 );