Created
February 2, 2015 08:49
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 thewp_get_attachment_image_src();
function, once for each declared thumbnail size and once again for the custom size. I've found that theif
statement on line 78 is unreliable andimage_make_intermediate_size()
on line 82 is being run multiple times per call to thewp_get_attachment_image_src();
function, even when the custom image size already exists. The custom image is generated over and over again every timewp_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, andimage_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.