Skip to content

Instantly share code, notes, and snippets.

@timkinnane
Created February 2, 2015 08:49
Show Gist options
  • Save timkinnane/e82eb87d9cc489620b80 to your computer and use it in GitHub Desktop.
Save timkinnane/e82eb87d9cc489620b80 to your computer and use it in GitHub Desktop.
Filter the output of image_downsize() to return dynamically generated images for intermediate or inline sizes. Long description in php doc block.
<?php
/**
* Filter the output of image_downsize() to return dynamically generated images for intermediate or inline sizes.
*
* <p>Because Wordpress generates all image sizes on first upload, if you change
* theme or size settings after the upload, there won't be a matching file for
* the requested size.<br/>
* This filter addresses the problem of the default downsize process laoding
* a large file and scaling it down in the browser if it doesn't find the right
* size image. This can cause large files to be loaded unnecessarily and will
* only scale it to a max width or height using the proportions of the next best
* image, it won't crop to the exact dimensions.</p>
* <p>Other solutions involve either patching Wordpress, using custom functions or
* using plugins that regenerate images manually. They work with some success but
* don't satisfy all the following ideal (imo) criteria:</p>
* <ul>
* <li>Regenerate images automatically when required by the current theme</li>
* <li>Don't require a plugin to manage media and extra images sizes</li>
* <li>Don't use depreciated or custom image processing functions</li>
* <li>Let wordpress handle creation, paths, error checking, library detection</li>
* <li>Allow other filters to still apply (don't bypass filtered functions)</li>
* <li>Keep generated sizes in meta to avoid orphan files in uploads folder</li>
* <li>Can be disabled/removed without errors or changing anything else</li>
* </ul>
* <p>This does all that! :D</p>
* <p>The issue was well defined in 2010 by Victor Teixeira and subsiquent
* contributor repos, more recently by Eric Lewis. See links.</p>
* <p>Example usage with theme specific size:
* <br/>In theme functions.php: `add_image_size( 'Feature Image', 565, 337, true );`
* <br/>In template file: `the_post_thumbnail( 'Feature Image' );`</p>
* <p>Example usage with on-the-fly dimensions:<br/>
* <br/>In template file: `the_post_thumbnail( array( 565, 337 ) );`
* <br/>Will create a new named size called '565x337'</p>
* <p>Returning a truthy value to the filter will effectively short-circuit
* down-sizing the image, returning that value as output instead.</p>
*
* @author Tim Kinnane <tim@nestedcode.com>
* @link http://nestedcode.com
* @link https://core.trac.wordpress.org/ticket/15311
* @link https://gist.github.com/seedprod/1367237
* @link https://core.trac.wordpress.org/attachment/ticket/15311/15311.7.diff*
* @todo This approach would be improved by using a cron job to delete images
* for any currently undefined sizes, to save space after changing theme.
* that would also require removing the matching size meta on the attachment.
* @param bool $downsize Whether to short-circuit the image downsize. Default passed in true. (ignored in filter)
* @param int $id Attachment ID for image.
* @param array|string $size Size of image, either array or string. Default passed in 'medium'.
* @return array|bool [ Image URL, width, height, bool ($is_intermediate) ] OR false if not resizing
*/
function dynamic_image_downsize( $downsize=true, $id, $size, $crop=true ) {
$meta = wp_get_attachment_metadata( $id );
$sizes = get_image_sizes();
// use specific w/h dimensions requested
if ( is_array( $size ) && sizeof( $size ) == 2 ) {
list( $width, $height ) = $size;
// make a size name from requested dimensions as a fallback for saving to meta
$size = $width.'x'.$height;
// if dimensions match a named size, use that instead
foreach ( $sizes as $size_name => $size_atts ) {
if ( $width == $size_atts['width'] && $height == $size_atts['height'] )
$size = $size_name;
}
// or get key/values (width, height, crop) from named size
} elseif ( array_key_exists( $size, $sizes ) ) {
extract( $sizes[$size] );
// unrecognized size, exit to handle as normal
} else {
return false;
}
// exit if there's already a generated image with the right dimensions
// the default downsize function would use it anyway (even if it had a different name)
if ( array_key_exists( $size, $meta['sizes'] ) && $width == $meta['sizes'][$size]['width'] && $height == $meta['sizes'][$size]['height'] )
return false;
// nothing matching size exists, generate and save new image from original
$intermediate = image_make_intermediate_size( get_attached_file( $id ), $width, $height, $crop );
// exit if failed creating image
if ( !is_array( $intermediate ) )
return false;
// save the new size parameters in meta (to find it next time)
$meta['sizes'][$size] = $intermediate;
wp_update_attachment_metadata( $id, $meta );
// this step is from the default image_downsize function in media.php
// "might need to further constrain it if content_width is narrower"
list( $width, $height ) = image_constrain_size_for_editor( $intermediate['width'], $intermediate['height'], $size );
// use path of original file with new filename
$original_url = wp_get_attachment_url( $id );
$original_basename = wp_basename( $original_url );
$img_url = str_replace($original_basename, $intermediate['file'], $original_url);
// 'Tis done, and here's the image
return array( $img_url, $width, $height, true );
}
add_filter( 'image_downsize', 'dynamic_image_downsize', 10, 3 );
// Useful helper function from codex example: http://codex.wordpress.org/Function_Reference/get_intermediate_image_sizes
function get_image_sizes( $size = '' ) {
global $_wp_additional_image_sizes;
$sizes = array();
$get_intermediate_image_sizes = get_intermediate_image_sizes();
// Create the full array with sizes and crop info
foreach( $get_intermediate_image_sizes as $_size ) {
if ( in_array( $_size, array( 'thumbnail', 'medium', 'large' ) ) ) {
$sizes[ $_size ]['width'] = get_option( $_size . '_size_w' );
$sizes[ $_size ]['height'] = get_option( $_size . '_size_h' );
$sizes[ $_size ]['crop'] = (bool) get_option( $_size . '_crop' );
} elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) {
$sizes[ $_size ] = array(
'width' => $_wp_additional_image_sizes[ $_size ]['width'],
'height' => $_wp_additional_image_sizes[ $_size ]['height'],
'crop' => $_wp_additional_image_sizes[ $_size ]['crop']
);
}
}
// Get only 1 size if found
if ( $size ) {
if( isset( $sizes[ $size ] ) )
return $sizes[ $size ];
else
return false;
}
return $sizes;
}
?>
@daltonrooney
Copy link

This filter is a great idea, I've been looking for the perfect dynamic image resizer for WordPress forever. In testing your script, however, I found some performance issues. It seems that the image_downsize filter is run multiple times for each call to the wp_get_attachment_image_src(); function, once for each declared thumbnail size and once again for the custom size. I've found that the if statement on line 78 is unreliable and image_make_intermediate_size() on line 82 is being run multiple times per call to the wp_get_attachment_image_src(); function, even when the custom image size already exists. The custom image is generated over and over again every time wp_get_attachment_image_src() is run. This is super slow, especially on a page with more than a few images.

I've temporarily fixed this situation by bailing out of the dynamic_image_downsize function altogether if the $size variable isn't an array. That ensures that the function is only run for my 'on the fly' defined image sizes, and image_make_intermediate_size() is only called the first time it's run. This seems to have solved my performance problem.

I hope this makes sense - let me know if you need any additional info.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment