if ( function_exists( 'imageavif' ) ) {
return imageavif( $image, $filename );
* Image preview ratio. Internal use only.
* @param int $w Image width in pixels.
* @param int $h Image height in pixels.
* @return float|int Image preview ratio.
function _image_get_preview_ratio( $w, $h ) {
return $max > 600 ? ( 600 / $max ) : 1;
* Returns an image resource. Internal use only.
* @deprecated 3.5.0 Use WP_Image_Editor::rotate()
* @see WP_Image_Editor::rotate()
* @param resource|GdImage $img Image resource.
* @param float|int $angle Image rotation angle, in degrees.
* @return resource|GdImage|false GD image resource or GdImage instance, false otherwise.
function _rotate_image_resource( $img, $angle ) {
_deprecated_function( __FUNCTION__, '3.5.0', 'WP_Image_Editor::rotate()' );
if ( function_exists( 'imagerotate' ) ) {
$rotated = imagerotate( $img, $angle, 0 );
if ( is_gd_image( $rotated ) ) {
if ( PHP_VERSION_ID < 80000 ) { // imagedestroy() has no effect as of PHP 8.0.
* Flips an image resource. Internal use only.
* @deprecated 3.5.0 Use WP_Image_Editor::flip()
* @see WP_Image_Editor::flip()
* @param resource|GdImage $img Image resource or GdImage instance.
* @param bool $horz Whether to flip horizontally.
* @param bool $vert Whether to flip vertically.
* @return resource|GdImage (maybe) flipped image resource or GdImage instance.
function _flip_image_resource( $img, $horz, $vert ) {
_deprecated_function( __FUNCTION__, '3.5.0', 'WP_Image_Editor::flip()' );
$dst = wp_imagecreatetruecolor( $w, $h );
if ( is_gd_image( $dst ) ) {
$sx = $vert ? ( $w - 1 ) : 0;
$sy = $horz ? ( $h - 1 ) : 0;
if ( imagecopyresampled( $dst, $img, 0, 0, $sx, $sy, $w, $h, $sw, $sh ) ) {
if ( PHP_VERSION_ID < 80000 ) { // imagedestroy() has no effect as of PHP 8.0.
* Crops an image resource. Internal use only.
* @param resource|GdImage $img Image resource or GdImage instance.
* @param float $x Source point x-coordinate.
* @param float $y Source point y-coordinate.
* @param float $w Source width.
* @param float $h Source height.
* @return resource|GdImage (maybe) cropped image resource or GdImage instance.
function _crop_image_resource( $img, $x, $y, $w, $h ) {
$dst = wp_imagecreatetruecolor( $w, $h );
if ( is_gd_image( $dst ) ) {
if ( imagecopy( $dst, $img, 0, 0, $x, $y, $w, $h ) ) {
if ( PHP_VERSION_ID < 80000 ) { // imagedestroy() has no effect as of PHP 8.0.
* Performs group of changes on Editor specified.
* @param WP_Image_Editor $image WP_Image_Editor instance.
* @param array $changes Array of change operations.
* @return WP_Image_Editor WP_Image_Editor instance with changes applied.
function image_edit_apply_changes( $image, $changes ) {
if ( is_gd_image( $image ) ) {
/* translators: 1: $image, 2: WP_Image_Editor */
_deprecated_argument( __FUNCTION__, '3.5.0', sprintf( __( '%1$s needs to be a %2$s object.' ), '$image', 'WP_Image_Editor' ) );
if ( ! is_array( $changes ) ) {
// Expand change operations.
foreach ( $changes as $key => $obj ) {
if ( isset( $obj->r ) ) {
} elseif ( isset( $obj->f ) ) {
} elseif ( isset( $obj->c ) ) {
if ( count( $changes ) > 1 ) {
$filtered = array( $changes[0] );
for ( $i = 0, $j = 1, $c = count( $changes ); $j < $c; $j++ ) {
if ( $filtered[ $i ]->type === $changes[ $j ]->type ) {
switch ( $filtered[ $i ]->type ) {
$filtered[ $i ]->angle += $changes[ $j ]->angle;
$filtered[ $i ]->axis ^= $changes[ $j ]->axis;
$filtered[ ++$i ] = $changes[ $j ];
// Image resource before applying the changes.
if ( $image instanceof WP_Image_Editor ) {
* Filters the WP_Image_Editor instance before applying changes to the image.
* @param WP_Image_Editor $image WP_Image_Editor instance.
* @param array $changes Array of change operations.
$image = apply_filters( 'wp_image_editor_before_change', $image, $changes );
} elseif ( is_gd_image( $image ) ) {
* Filters the GD image resource before applying changes to the image.
* @deprecated 3.5.0 Use {@see 'wp_image_editor_before_change'} instead.
* @param resource|GdImage $image GD image resource or GdImage instance.
* @param array $changes Array of change operations.
$image = apply_filters_deprecated( 'image_edit_before_change', array( $image, $changes ), '3.5.0', 'wp_image_editor_before_change' );
foreach ( $changes as $operation ) {
switch ( $operation->type ) {
if ( 0 !== $operation->angle ) {
if ( $image instanceof WP_Image_Editor ) {
$image->rotate( $operation->angle );
$image = _rotate_image_resource( $image, $operation->angle );
if ( 0 !== $operation->axis ) {
if ( $image instanceof WP_Image_Editor ) {
$image->flip( ( $operation->axis & 1 ) !== 0, ( $operation->axis & 2 ) !== 0 );
$image = _flip_image_resource( $image, ( $operation->axis & 1 ) !== 0, ( $operation->axis & 2 ) !== 0 );
if ( $image instanceof WP_Image_Editor ) {
$size = $image->get_size();
$scale = isset( $sel->r ) ? $sel->r : 1 / _image_get_preview_ratio( $w, $h ); // Discard preview scaling.
$image->crop( (int) ( $sel->x * $scale ), (int) ( $sel->y * $scale ), (int) ( $sel->w * $scale ), (int) ( $sel->h * $scale ) );
$scale = isset( $sel->r ) ? $sel->r : 1 / _image_get_preview_ratio( imagesx( $image ), imagesy( $image ) ); // Discard preview scaling.
$image = _crop_image_resource( $image, $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale );
* Streams image in post to browser, along with enqueued changes
* in `$_REQUEST['history']`.
* @param int $post_id Attachment post ID.
* @return bool True on success, false on failure.
function stream_preview_image( $post_id ) {
$post = get_post( $post_id );
wp_raise_memory_limit( 'admin' );
$img = wp_get_image_editor( _load_image_to_edit_path( $post_id ) );
if ( is_wp_error( $img ) ) {
$changes = ! empty( $_REQUEST['history'] ) ? json_decode( wp_unslash( $_REQUEST['history'] ) ) : null;
$img = image_edit_apply_changes( $img, $changes );
$size = $img->get_size();
$ratio = _image_get_preview_ratio( $w, $h );
$w2 = max( 1, $w * $ratio );
$h2 = max( 1, $h * $ratio );
if ( is_wp_error( $img->resize( $w2, $h2 ) ) ) {
return wp_stream_image( $img, $post->post_mime_type, $post_id );
* Restores the metadata for a given attachment.
* @param int $post_id Attachment post ID.
* @return stdClass Image restoration message object.
function wp_restore_image( $post_id ) {
$meta = wp_get_attachment_metadata( $post_id );
$file = get_attached_file( $post_id );
$backup_sizes = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
$old_backup_sizes = $backup_sizes;
if ( ! is_array( $backup_sizes ) ) {
$msg->error = __( 'Cannot load image metadata.' );
$parts = pathinfo( $file );
$suffix = time() . rand( 100, 999 );
$default_sizes = get_intermediate_image_sizes();
if ( isset( $backup_sizes['full-orig'] ) && is_array( $backup_sizes['full-orig'] ) ) {
$data = $backup_sizes['full-orig'];
if ( $parts['basename'] !== $data['file'] ) {
if ( defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE ) {
// Delete only if it's an edited image.
if ( preg_match( '/-e[0-9]{13}\./', $parts['basename'] ) ) {
} elseif ( isset( $meta['width'], $meta['height'] ) ) {
$backup_sizes[ "full-$suffix" ] = array(
'width' => $meta['width'],
'height' => $meta['height'],
'filesize' => $meta['filesize'],
'file' => $parts['basename'],
$restored_file = path_join( $parts['dirname'], $data['file'] );
$restored = update_attached_file( $post_id, $restored_file );
$meta['file'] = _wp_relative_upload_path( $restored_file );
$meta['width'] = $data['width'];
$meta['height'] = $data['height'];
if ( isset( $data['filesize'] ) ) {
* Restore the original filesize if it was backed up.
* See https://core.trac.wordpress.org/ticket/59684.
$meta['filesize'] = $data['filesize'];
foreach ( $default_sizes as $default_size ) {
if ( isset( $backup_sizes[ "$default_size-orig" ] ) ) {
$data = $backup_sizes[ "$default_size-orig" ];
if ( isset( $meta['sizes'][ $default_size ] ) && $meta['sizes'][ $default_size ]['file'] !== $data['file'] ) {
if ( defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE ) {
// Delete only if it's an edited image.
if ( preg_match( '/-e[0-9]{13}-/', $meta['sizes'][ $default_size ]['file'] ) ) {
$delete_file = path_join( $parts['dirname'], $meta['sizes'][ $default_size ]['file'] );
wp_delete_file( $delete_file );
$backup_sizes[ "$default_size-{$suffix}" ] = $meta['sizes'][ $default_size ];
$meta['sizes'][ $default_size ] = $data;
unset( $meta['sizes'][ $default_size ] );
if ( ! wp_update_attachment_metadata( $post_id, $meta )
|| ( $old_backup_sizes !== $backup_sizes && ! update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes ) )
$msg->error = __( 'Cannot save image metadata.' );
$msg->error = __( 'Image metadata is inconsistent.' );
$msg->msg = __( 'Image restored successfully.' );
if ( defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE ) {
delete_post_meta( $post_id, '_wp_attachment_backup_sizes' );
* Saves image to post, along with enqueued changes
* in `$_REQUEST['history']`.
* @param int $post_id Attachment post ID.
function wp_save_image( $post_id ) {
$_wp_additional_image_sizes = wp_get_additional_image_sizes();
$return = new stdClass();
$post = get_post( $post_id );
$img = wp_get_image_editor( _load_image_to_edit_path( $post_id, 'full' ) );
if ( is_wp_error( $img ) ) {
$return->error = esc_js( __( 'Unable to create new image.' ) );
$full_width = ! empty( $_REQUEST['fwidth'] ) ? (int) $_REQUEST['fwidth'] : 0;
$full_height = ! empty( $_REQUEST['fheight'] ) ? (int) $_REQUEST['fheight'] : 0;
$target = ! empty( $_REQUEST['target'] ) ? preg_replace( '/[^a-z0-9_-]+/i', '', $_REQUEST['target'] ) : '';
$scale = ! empty( $_REQUEST['do'] ) && 'scale' === $_REQUEST['do'];
/** This filter is documented in wp-admin/includes/image-edit.php */
$edit_thumbnails_separately = (bool) apply_filters( 'image_edit_thumbnails_separately', false );
$size = $img->get_size();
$original_width = $size['width'];
$original_height = $size['height'];
if ( $full_width > $original_width || $full_height > $original_height ) {
$return->error = esc_js( __( 'Images cannot be scaled to a size larger than the original.' ) );
if ( $full_width > 0 && $full_height > 0 ) {
// Check if it has roughly the same w / h ratio.
$diff = round( $original_width / $original_height, 2 ) - round( $full_width / $full_height, 2 );
if ( -0.1 < $diff && $diff < 0.1 ) {
// Scale the full size image.
if ( $img->resize( $full_width, $full_height ) ) {
$return->error = esc_js( __( 'Error while saving the scaled image. Please reload the page and try again.' ) );
} elseif ( ! empty( $_REQUEST['history'] ) ) {
$changes = json_decode( wp_unslash( $_REQUEST['history'] ) );
$img = image_edit_apply_changes( $img, $changes );
$return->error = esc_js( __( 'Nothing to save, the image has not changed.' ) );
$meta = wp_get_attachment_metadata( $post_id );
$backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
if ( ! is_array( $meta ) ) {
$return->error = esc_js( __( 'Image data does not exist. Please re-upload the image.' ) );
if ( ! is_array( $backup_sizes ) ) {
// Generate new filename.
$path = get_attached_file( $post_id );
$basename = pathinfo( $path, PATHINFO_BASENAME );
$dirname = pathinfo( $path, PATHINFO_DIRNAME );
$ext = pathinfo( $path, PATHINFO_EXTENSION );
$filename = pathinfo( $path, PATHINFO_FILENAME );
$suffix = time() . rand( 100, 999 );
if ( defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE
&& isset( $backup_sizes['full-orig'] ) && $backup_sizes['full-orig']['file'] !== $basename
if ( $edit_thumbnails_separately && 'thumbnail' === $target ) {
$new_path = "{$dirname}/{$filename}-temp.{$ext}";