* File contains all the administration image manipulation functions.
* @subpackage Administration
* Crops an image to a given size.
* @param string|int $src The source file or Attachment ID.
* @param int $src_x The start x position to crop from.
* @param int $src_y The start y position to crop from.
* @param int $src_w The width to crop.
* @param int $src_h The height to crop.
* @param int $dst_w The destination width.
* @param int $dst_h The destination height.
* @param bool|false $src_abs Optional. If the source crop points are absolute.
* @param string|false $dst_file Optional. The destination file to write to.
* @return string|WP_Error New filepath on success, WP_Error on failure.
function wp_crop_image( $src, $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs = false, $dst_file = false ) {
if ( is_numeric( $src ) ) { // Handle int as attachment ID.
$src_file = get_attached_file( $src );
if ( ! file_exists( $src_file ) ) {
// If the file doesn't exist, attempt a URL fopen on the src link.
// This can occur with certain file replication plugins.
$src = _load_image_to_edit_path( $src, 'full' );
$editor = wp_get_image_editor( $src );
if ( is_wp_error( $editor ) ) {
$src = $editor->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs );
if ( is_wp_error( $src ) ) {
$dst_file = str_replace( wp_basename( $src_file ), 'cropped-' . wp_basename( $src_file ), $src_file );
* The directory containing the original file may no longer exist when
* using a replication plugin.
wp_mkdir_p( dirname( $dst_file ) );
$dst_file = dirname( $dst_file ) . '/' . wp_unique_filename( dirname( $dst_file ), wp_basename( $dst_file ) );
$result = $editor->save( $dst_file );
if ( is_wp_error( $result ) ) {
* Compare the existing image sub-sizes (as saved in the attachment meta)
* to the currently registered image sub-sizes, and return the difference.
* Registered sub-sizes that are larger than the image are skipped.
* @param int $attachment_id The image attachment post ID.
* @return array An array of the image sub-sizes that are currently defined but don't exist for this image.
function wp_get_missing_image_subsizes( $attachment_id ) {
if ( ! wp_attachment_is_image( $attachment_id ) ) {
$registered_sizes = wp_get_registered_image_subsizes();
$image_meta = wp_get_attachment_metadata( $attachment_id );
if ( empty( $image_meta ) ) {
return $registered_sizes;
// Use the originally uploaded image dimensions as full_width and full_height.
if ( ! empty( $image_meta['original_image'] ) ) {
$image_file = wp_get_original_image_path( $attachment_id );
$imagesize = wp_getimagesize( $image_file );
if ( ! empty( $imagesize ) ) {
$full_width = $imagesize[0];
$full_height = $imagesize[1];
$full_width = (int) $image_meta['width'];
$full_height = (int) $image_meta['height'];
$possible_sizes = array();
// Skip registered sizes that are too large for the uploaded image.
foreach ( $registered_sizes as $size_name => $size_data ) {
if ( image_resize_dimensions( $full_width, $full_height, $size_data['width'], $size_data['height'], $size_data['crop'] ) ) {
$possible_sizes[ $size_name ] = $size_data;
if ( empty( $image_meta['sizes'] ) ) {
$image_meta['sizes'] = array();
* Remove sizes that already exist. Only checks for matching "size names".
* It is possible that the dimensions for a particular size name have changed.
* For example the user has changed the values on the Settings -> Media screen.
* However we keep the old sub-sizes with the previous dimensions
* as the image may have been used in an older post.
$missing_sizes = array_diff_key( $possible_sizes, $image_meta['sizes'] );
* Filters the array of missing image sub-sizes for an uploaded image.
* @param array $missing_sizes Array with the missing image sub-sizes.
* @param array $image_meta The image meta data.
* @param int $attachment_id The image attachment post ID.
return apply_filters( 'wp_get_missing_image_subsizes', $missing_sizes, $image_meta, $attachment_id );
* If any of the currently registered image sub-sizes are missing,
* create them and update the image meta data.
* @param int $attachment_id The image attachment post ID.
* @return array|WP_Error The updated image meta data array or WP_Error object
* if both the image meta and the attached file are missing.
function wp_update_image_subsizes( $attachment_id ) {
$image_meta = wp_get_attachment_metadata( $attachment_id );
$image_file = wp_get_original_image_path( $attachment_id );
if ( empty( $image_meta ) || ! is_array( $image_meta ) ) {
// Previously failed upload?
// If there is an uploaded file, make all sub-sizes and generate all of the attachment meta.
if ( ! empty( $image_file ) ) {
$image_meta = wp_create_image_subsizes( $image_file, $attachment_id );
return new WP_Error( 'invalid_attachment', __( 'The attached file cannot be found.' ) );
$missing_sizes = wp_get_missing_image_subsizes( $attachment_id );
if ( empty( $missing_sizes ) ) {
// This also updates the image meta.
$image_meta = _wp_make_subsizes( $missing_sizes, $image_file, $image_meta, $attachment_id );
/** This filter is documented in wp-admin/includes/image.php */
$image_meta = apply_filters( 'wp_generate_attachment_metadata', $image_meta, $attachment_id, 'update' );
// Save the updated metadata.
wp_update_attachment_metadata( $attachment_id, $image_meta );
* Updates the attached file and image meta data when the original image was edited.
* @param array $saved_data The data returned from WP_Image_Editor after successfully saving an image.
* @param string $original_file Path to the original file.
* @param array $image_meta The image meta data.
* @param int $attachment_id The attachment post ID.
* @return array The updated image meta data.
function _wp_image_meta_replace_original( $saved_data, $original_file, $image_meta, $attachment_id ) {
$new_file = $saved_data['path'];
// Update the attached file meta.
update_attached_file( $attachment_id, $new_file );
// Width and height of the new image.
$image_meta['width'] = $saved_data['width'];
$image_meta['height'] = $saved_data['height'];
// Make the file path relative to the upload dir.
$image_meta['file'] = _wp_relative_upload_path( $new_file );
// Store the original image file name in image_meta.
$image_meta['original_image'] = wp_basename( $original_file );
* Creates image sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata.
* Intended for use after an image is uploaded. Saves/updates the image metadata after each
* sub-size is created. If there was an error, it is added to the returned image metadata array.
* @param string $file Full path to the image file.
* @param int $attachment_id Attachment Id to process.
* @return array The image attachment meta data.
function wp_create_image_subsizes( $file, $attachment_id ) {
$imagesize = wp_getimagesize( $file );
if ( empty( $imagesize ) ) {
'width' => $imagesize[0],
'height' => $imagesize[1],
'file' => _wp_relative_upload_path( $file ),
// Fetch additional metadata from EXIF/IPTC.
$exif_meta = wp_read_image_metadata( $file );
$image_meta['image_meta'] = $exif_meta;
// Do not scale (large) PNG images. May result in sub-sizes that have greater file size than the original. See #48736.
if ( 'image/png' !== $imagesize['mime'] ) {
* Filters the "BIG image" threshold value.
* If the original image width or height is above the threshold, it will be scaled down. The threshold is
* used as max width and max height. The scaled down image will be used as the largest available size, including
* the `_wp_attached_file` post meta value.
* Returning `false` from the filter callback will disable the scaling.
* @param int $threshold The threshold value in pixels. Default 2560.
* @param array $imagesize {
* Indexed array of the image width and height in pixels.
* @type int $0 The image width.
* @type int $1 The image height.
* @param string $file Full path to the uploaded image file.
* @param int $attachment_id Attachment post ID.
$threshold = (int) apply_filters( 'big_image_size_threshold', 2560, $imagesize, $file, $attachment_id );
// If the original image's dimensions are over the threshold,
// scale the image and use it as the "full" size.
if ( $threshold && ( $image_meta['width'] > $threshold || $image_meta['height'] > $threshold ) ) {
$editor = wp_get_image_editor( $file );
if ( is_wp_error( $editor ) ) {
// This image cannot be edited.
$resized = $editor->resize( $threshold, $threshold );
// If there is EXIF data, rotate according to EXIF Orientation.
if ( ! is_wp_error( $resized ) && is_array( $exif_meta ) ) {
$resized = $editor->maybe_exif_rotate();
if ( ! is_wp_error( $resized ) ) {
// Append "-scaled" to the image file name. It will look like "my_image-scaled.jpg".
// This doesn't affect the sub-sizes names as they are generated from the original image (for best quality).
$saved = $editor->save( $editor->generate_filename( 'scaled' ) );
if ( ! is_wp_error( $saved ) ) {
$image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
// If the image was rotated update the stored EXIF data.
if ( true === $rotated && ! empty( $image_meta['image_meta']['orientation'] ) ) {
$image_meta['image_meta']['orientation'] = 1;
} elseif ( ! empty( $exif_meta['orientation'] ) && 1 !== (int) $exif_meta['orientation'] ) {
// Rotate the whole original image if there is EXIF data and "orientation" is not 1.
$editor = wp_get_image_editor( $file );
if ( is_wp_error( $editor ) ) {
// This image cannot be edited.
$rotated = $editor->maybe_exif_rotate();
if ( true === $rotated ) {
// Append `-rotated` to the image file name.
$saved = $editor->save( $editor->generate_filename( 'rotated' ) );
if ( ! is_wp_error( $saved ) ) {
$image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
// Update the stored EXIF data.
if ( ! empty( $image_meta['image_meta']['orientation'] ) ) {
$image_meta['image_meta']['orientation'] = 1;
* Initial save of the new metadata.
* At this point the file was uploaded and moved to the uploads directory
* but the image sub-sizes haven't been created yet and the `sizes` array is empty.
wp_update_attachment_metadata( $attachment_id, $image_meta );
$new_sizes = wp_get_registered_image_subsizes();
* Filters the image sizes automatically generated when uploading an image.
* @since 4.4.0 Added the `$image_meta` argument.
* @since 5.3.0 Added the `$attachment_id` argument.
* @param array $new_sizes Associative array of image sizes to be created.
* @param array $image_meta The image meta data: width, height, file, sizes, etc.
* @param int $attachment_id The attachment post ID for the image.
$new_sizes = apply_filters( 'intermediate_image_sizes_advanced', $new_sizes, $image_meta, $attachment_id );
return _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id );
* Low-level function to create image sub-sizes.
* Updates the image meta after each sub-size is created.
* Errors are stored in the returned image metadata array.
* @param array $new_sizes Array defining what sizes to create.
* @param string $file Full path to the image file.
* @param array $image_meta The attachment meta data array.
* @param int $attachment_id Attachment Id to process.
* @return array The attachment meta data with updated `sizes` array. Includes an array of errors encountered while resizing.
function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) {
if ( empty( $image_meta ) || ! is_array( $image_meta ) ) {
// Not an image attachment.
// Check if any of the new sizes already exist.
if ( isset( $image_meta['sizes'] ) && is_array( $image_meta['sizes'] ) ) {
foreach ( $image_meta['sizes'] as $size_name => $size_meta ) {
* Only checks "size name" so we don't override existing images even if the dimensions
* don't match the currently defined size with the same name.
* To change the behavior, unset changed/mismatched sizes in the `sizes` array in image meta.
if ( array_key_exists( $size_name, $new_sizes ) ) {
unset( $new_sizes[ $size_name ] );
$image_meta['sizes'] = array();
if ( empty( $new_sizes ) ) {
* Sort the image sub-sizes in order of priority when creating them.
* This ensures there is an appropriate sub-size the user can access immediately
* even when there was an error and not all sub-sizes were created.
$new_sizes = array_filter( array_merge( $priority, $new_sizes ) );
$editor = wp_get_image_editor( $file );
if ( is_wp_error( $editor ) ) {
// The image cannot be edited.
// If stored EXIF data exists, rotate the source image before creating sub-sizes.
if ( ! empty( $image_meta['image_meta'] ) ) {
$rotated = $editor->maybe_exif_rotate();
if ( is_wp_error( $rotated ) ) {
if ( method_exists( $editor, 'make_subsize' ) ) {
foreach ( $new_sizes as $new_size_name => $new_size_data ) {
$new_size_meta = $editor->make_subsize( $new_size_data );
if ( is_wp_error( $new_size_meta ) ) {
// Save the size meta value.
$image_meta['sizes'][ $new_size_name ] = $new_size_meta;
wp_update_attachment_metadata( $attachment_id, $image_meta );
// Fall back to `$editor->multi_resize()`.
$created_sizes = $editor->multi_resize( $new_sizes );
if ( ! empty( $created_sizes ) ) {
$image_meta['sizes'] = array_merge( $image_meta['sizes'], $created_sizes );
wp_update_attachment_metadata( $attachment_id, $image_meta );
* Generate attachment meta data and create image sub-sizes for images.
* @param int $attachment_id Attachment Id to process.
* @param string $file Filepath of the Attached image.
* @return mixed Metadata for attachment.
function wp_generate_attachment_metadata( $attachment_id, $file ) {
$attachment = get_post( $attachment_id );
$mime_type = get_post_mime_type( $attachment );
if ( preg_match( '!^image/!', $mime_type ) && file_is_displayable_image( $file ) ) {
// Make thumbnails and other intermediate sizes.
$metadata = wp_create_image_subsizes( $file, $attachment_id );
} elseif ( wp_attachment_is( 'video', $attachment ) ) {
$metadata = wp_read_video_metadata( $file );
$support = current_theme_supports( 'post-thumbnails', 'attachment:video' ) || post_type_supports( 'attachment:video', 'thumbnail' );
} elseif ( wp_attachment_is( 'audio', $attachment ) ) {
$metadata = wp_read_audio_metadata( $file );
$support = current_theme_supports( 'post-thumbnails', 'attachment:audio' ) || post_type_supports( 'attachment:audio', 'thumbnail' );
if ( $support && ! empty( $metadata['image']['data'] ) ) {
// Check for existing cover.
$hash = md5( $metadata['image']['data'] );
'post_type' => 'attachment',
'post_mime_type' => $metadata['image']['mime'],