diff --git src/js/_enqueues/vendor/plupload/handlers.js src/js/_enqueues/vendor/plupload/handlers.js
index fa602daf43..c7e3c253d3 100644
--- src/js/_enqueues/vendor/plupload/handlers.js
+++ src/js/_enqueues/vendor/plupload/handlers.js
@@ -486,7 +486,7 @@ jQuery( document ).ready( function( $ ) {
 
 		times = tryAgainCount[ file.id ];
 
-		if ( times && times > 4 ) {
+		if ( times && times > 8 ) {
 			/*
 			 * The file may have been uploaded and attachment post created,
 			 * but post-processing and resizing failed...
diff --git src/js/_enqueues/vendor/plupload/wp-plupload.js src/js/_enqueues/vendor/plupload/wp-plupload.js
index 0fdebf77d1..217b3c09e2 100644
--- src/js/_enqueues/vendor/plupload/wp-plupload.js
+++ src/js/_enqueues/vendor/plupload/wp-plupload.js
@@ -138,7 +138,7 @@ window.wp = window.wp || {};
 
 			times = tryAgainCount[ file.id ];
 
-			if ( times && times > 4 ) {
+			if ( times && times > 8 ) {
 				/*
 				 * The file may have been uploaded and attachment post created,
 				 * but post-processing and resizing failed...
diff --git src/wp-admin/includes/image.php src/wp-admin/includes/image.php
index 1a9c9e2e9c..75b4e48562 100644
--- src/wp-admin/includes/image.php
+++ src/wp-admin/includes/image.php
@@ -77,16 +77,23 @@ function wp_crop_image( $src, $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $s
  * Registered sub-sizes that are larger than the image are skipped.
  *
  * @since 5.3.0
+ * @since 6.1.0 The $mime_type parameter was added.
  *
- * @param int $attachment_id The image attachment post ID.
+ * @param int    $attachment_id The image attachment post ID.
+ * @param string $mime_type     Optional. The mime type to check for missing sizes. Default is the primary image mime.
  * @return array[] Associative array of arrays of image sub-size information for
  *                 missing image sizes, keyed by image size name.
  */
-function wp_get_missing_image_subsizes( $attachment_id ) {
+function wp_get_missing_image_subsizes( $attachment_id, $mime_type = '' ) {
 	if ( ! wp_attachment_is_image( $attachment_id ) ) {
 		return array();
 	}
 
+	$primary_mime_type = get_post_mime_type( get_post( $attachment_id ) );
+	if ( ! $mime_type ) {
+		$mime_type = $primary_mime_type;
+	}
+
 	$registered_sizes = wp_get_registered_image_subsizes();
 	$image_meta       = wp_get_attachment_metadata( $attachment_id );
 
@@ -129,19 +136,38 @@ function wp_get_missing_image_subsizes( $attachment_id ) {
 	 * 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'] );
+	$missing_sizes = array();
+	foreach ( $possible_sizes as $size_name => $size_data ) {
+		if ( ! isset( $image_meta['sizes'][ $size_name ] ) ) {
+			$missing_sizes[ $size_name ] = $size_data;
+			continue;
+		}
+
+		if ( ( isset( $size_data['mime-type'] ) && $size_data['mime-type'] === $mime_type ) || isset( $size_data['sources'][ $mime_type ] ) ) {
+			continue;
+		}
+
+		$missing_sizes[ $size_name ] = $size_data;
+	}
+
+	// Filter secondary mime types to those sizes that are enabled.
+	if ( $primary_mime_type !== $mime_type ) {
+		$missing_sizes = _wp_filter_image_sizes_additional_mime_type_support( $missing_sizes, $attachment_id );
+	}
 
 	/**
 	 * Filters the array of missing image sub-sizes for an uploaded image.
 	 *
 	 * @since 5.3.0
+	 * @since 6.1.0 The $mime_type filter parameter was added.
 	 *
 	 * @param array[] $missing_sizes Associative array of arrays of image sub-size information for
 	 *                               missing image sizes, keyed by image size name.
 	 * @param array   $image_meta    The image meta data.
 	 * @param int     $attachment_id The image attachment post ID.
+	 * @param string  $mime_type     The image mime type to get missing sizes for.
 	 */
-	return apply_filters( 'wp_get_missing_image_subsizes', $missing_sizes, $image_meta, $attachment_id );
+	return apply_filters( 'wp_get_missing_image_subsizes', $missing_sizes, $image_meta, $attachment_id, $mime_type );
 }
 
 /**
@@ -149,6 +175,7 @@ function wp_get_missing_image_subsizes( $attachment_id ) {
  * create them and update the image meta data.
  *
  * @since 5.3.0
+ * @since 6.1.0 Now supports additional mime types, creating the additional sub-sizes and 'full' sized images.
  *
  * @param int $attachment_id The image attachment post ID.
  * @return array|WP_Error The updated image meta data array or WP_Error object
@@ -167,14 +194,33 @@ function wp_update_image_subsizes( $attachment_id ) {
 			return new WP_Error( 'invalid_attachment', __( 'The attached file cannot be found.' ) );
 		}
 	} else {
-		$missing_sizes = wp_get_missing_image_subsizes( $attachment_id );
+		// Get the primary and additional mime types to generate.
+		list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $image_file, $attachment_id );
 
-		if ( empty( $missing_sizes ) ) {
-			return $image_meta;
+		// Generate missing 'full' image files for additional mime types.
+		if ( ! empty( $additional_mime_types ) ) {
+			if ( isset( $image_meta['sources'] ) ) {
+				$missing_mime_types = array_diff( $additional_mime_types, array_keys( $image_meta['sources'] ) );
+			} else {
+				$missing_mime_types = $additional_mime_types;
+			}
+			if ( ! empty( $missing_mime_types ) ) {
+				$image_meta = _wp_make_additional_mime_types( $missing_mime_types, $image_file, $image_meta, $attachment_id );
+			}
 		}
 
-		// This also updates the image meta.
-		$image_meta = _wp_make_subsizes( $missing_sizes, $image_file, $image_meta, $attachment_id );
+		// Generate missing image sub-sizes for each mime type.
+		$all_mime_types = array_merge( array( $primary_mime_type ), $additional_mime_types );
+		foreach ( $all_mime_types as $mime_type ) {
+			$missing_sizes = wp_get_missing_image_subsizes( $attachment_id, $mime_type );
+
+			if ( empty( $missing_sizes ) ) {
+				continue;
+			}
+
+			// This also updates the image meta.
+			$image_meta = _wp_make_subsizes( $missing_sizes, $image_file, $image_meta, $attachment_id, $mime_type );
+		}
 	}
 
 	/** This filter is documented in wp-admin/includes/image.php */
@@ -222,12 +268,13 @@ function _wp_image_meta_replace_original( $saved_data, $original_file, $image_me
 }
 
 /**
- * Creates image sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata.
+ * Creates image mime variations and 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.
  *
  * @since 5.3.0
+ * @since 6.1.0 Generates sub-sizes in alternate mime types based on the `wp_image_mime_transforms` filter.
  *
  * @param string $file          Full path to the image file.
  * @param int    $attachment_id Attachment ID to process.
@@ -248,6 +295,7 @@ function wp_create_image_subsizes( $file, $attachment_id ) {
 		'file'     => _wp_relative_upload_path( $file ),
 		'filesize' => wp_filesize( $file ),
 		'sizes'    => array(),
+		'sources'  => array(),
 	);
 
 	// Fetch additional metadata from EXIF/IPTC.
@@ -257,9 +305,112 @@ function wp_create_image_subsizes( $file, $attachment_id ) {
 		$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'] ) {
+	// Get the primary and additional mime types to generate.
+	list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $file, $attachment_id );
+
+	list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $primary_mime_type );
+	if ( is_wp_error( $editor ) ) {
+		return $image_meta;
+	}
+	$suffix = _wp_get_image_suffix( $resized, $rotated );
+
+	// Save image only if either it was modified or if the primary mime type is different from the original.
+	if ( ! empty( $suffix ) || $primary_mime_type !== $imagesize['mime'] ) {
+		$saved = $editor->save( $editor->generate_filename( $suffix ) );
+
+		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;
+			}
+		} else {
+			// TODO: Log errors.
+		}
+	}
+
+	// Set 'sources' for the primary mime type.
+	$image_meta['sources'][ $primary_mime_type ] = _wp_get_sources_from_meta( $image_meta );
 
+	/*
+	 * 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 );
+
+	if ( ! empty( $additional_mime_types ) ) {
+		// Use the original file's exif_meta orientation information for secondary mime generation.
+		$saved_orientation                       = $image_meta['image_meta']['orientation'];
+		$image_meta['image_meta']['orientation'] = $exif_meta['orientation'];
+		$image_meta                              = _wp_make_additional_mime_types( $additional_mime_types, $file, $image_meta, $attachment_id );
+		$image_meta['image_meta']['orientation'] = $saved_orientation;
+
+	}
+
+	$new_sizes = wp_get_registered_image_subsizes();
+
+	/**
+	 * Filters the image sizes automatically generated when uploading an image.
+	 *
+	 * @since 2.9.0
+	 * @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 );
+
+	$image_meta = _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id, $primary_mime_type );
+
+	// Filter secondary mime types to those sizes that are enabled.
+	$new_sizes = _wp_filter_image_sizes_additional_mime_type_support( $new_sizes, $attachment_id );
+
+	foreach ( $additional_mime_types as $additional_mime_type ) {
+		$image_meta = _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id, $additional_mime_type );
+	}
+
+	return $image_meta;
+}
+
+/**
+ * Returns a WP_Image_Editor instance where the image file has been scaled and rotated as necessary.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @param string     $file          Full path to the image file.
+ * @param int        $attachment_id Attachment ID.
+ * @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 array|null $exif_meta EXIF metadata if extracted from the image file.
+ * @param string     $mime_type Output mime type.
+ * @return array Array with three entries: The WP_Image_Editor instance, whether the image was resized, and whether the
+ *               image was rotated (booleans). Each entry can alternatively be a WP_Error in case something went wrong.
+ */
+function _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type ) {
+	$resized = false;
+	$rotated = false;
+
+	$editor = wp_get_image_editor( $file, array( 'mime_type' => $mime_type ) );
+	if ( is_wp_error( $editor ) ) {
+		// This image cannot be edited.
+		return array( $editor, $resized, $rotated );
+	}
+
+	if ( ! empty( $mime_type ) ) {
+		$editor->set_output_mime_type( $mime_type );
+	}
+
+	// Do not scale (large) PNG images. May result in sub-sizes that have greater file size than the original. See #48736.
+	if ( 'image/png' !== $mime_type ) {
 		/**
 		 * Filters the "BIG image" threshold value.
 		 *
@@ -285,96 +436,65 @@ function wp_create_image_subsizes( $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.
-				return $image_meta;
-			}
-
+		if ( $threshold && ( $imagesize[0] > $threshold || $imagesize[1] > $threshold ) ) {
 			// Resize the image.
 			$resized = $editor->resize( $threshold, $threshold );
-			$rotated = null;
 
 			// If there is EXIF data, rotate according to EXIF Orientation.
 			if ( ! is_wp_error( $resized ) && is_array( $exif_meta ) ) {
-				$resized = $editor->maybe_exif_rotate();
-				$rotated = $resized;
-			}
-
-			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;
-					}
-				} else {
-					// TODO: Log errors.
-				}
-			} else {
-				// TODO: Log errors.
+				$rotated = $editor->maybe_exif_rotate();
 			}
 		} 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.
-				return $image_meta;
-			}
-
-			// Rotate the image.
 			$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;
-					}
-				} else {
-					// TODO: Log errors.
-				}
-			}
 		}
 	}
 
-	/*
-	 * 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 );
+	return array( $editor, $resized, $rotated );
+}
 
-	$new_sizes = wp_get_registered_image_subsizes();
+/**
+ * Gets the suffix to use for image files based on resizing and rotating.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @param bool|WP_Error Whether the image was resized, or an error if resizing failed.
+ * @param bool|WP_Error Whether the image was rotated, or an error if rotating failed.
+ * @return string The suffix to use for the file name, or empty string if none.
+ */
+function _wp_get_image_suffix( $resized, $rotated ) {
+	if ( $resized && ! 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).
+		return 'scaled';
+	}
 
-	/**
-	 * Filters the image sizes automatically generated when uploading an image.
-	 *
-	 * @since 2.9.0
-	 * @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 );
+	if ( true === $rotated ) {
+		// Append `-rotated` to the image file name.
+		return 'rotated';
+	}
+
+	if ( is_wp_error( $resized ) || is_wp_error( $rotated ) ) {
+		// TODO: Log errors.
+	}
+	return '';
+}
 
-	return _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id );
+/**
+ * Gets a sources array element from a meta.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @param array $meta The meta to get the source from.
+ * @return array The source array element.
+ */
+function _wp_get_sources_from_meta( $meta ) {
+	return array(
+		'file'     => isset( $meta['file'] ) ? wp_basename( $meta['file'] ) : '',
+		'filesize' => isset( $meta['filesize'] ) ? $meta['filesize'] : wp_filesize( $meta['path'] ),
+	);
 }
 
 /**
@@ -384,30 +504,43 @@ function wp_create_image_subsizes( $file, $attachment_id ) {
  * Errors are stored in the returned image metadata array.
  *
  * @since 5.3.0
+ * @since 6.1.0 The $mime_type parameter was added.
  * @access private
  *
- * @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.
+ * @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.
+ * @param string $mime_type       Optional. The mime type to check for missing sizes. Default is the image mime of $file.
  * @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 ) {
+function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id, $mime_type = '' ) {
 	if ( empty( $image_meta ) || ! is_array( $image_meta ) ) {
 		// Not an image attachment.
 		return array();
 	}
 
+	$sizes = array();
+
+	$original_mime_type = wp_get_image_mime( $file );
+	if ( ! $mime_type ) {
+		$mime_type = $original_mime_type;
+	}
+
 	// 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 ) {
+	if ( isset( $sizes ) && is_array( $sizes ) ) {
+		foreach ( $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 ] );
+				// Unset the size if it is either the required mime type already exists either as main mime type or
+				// within sources.
+				if ( $size_meta['mime-type'] === $mime_type || isset( $size_meta['sources'][ $mime_type ] ) ) {
+					unset( $new_sizes[ $size_name ] );
+				}
 			}
 		}
 	} else {
@@ -433,13 +566,15 @@ function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) {
 
 	$new_sizes = array_filter( array_merge( $priority, $new_sizes ) );
 
-	$editor = wp_get_image_editor( $file );
+	$editor = wp_get_image_editor( $file, array( 'mime_type' => $mime_type ) );
 
 	if ( is_wp_error( $editor ) ) {
 		// The image cannot be edited.
 		return $image_meta;
 	}
 
+	$editor->set_output_mime_type( $mime_type );
+
 	// If stored EXIF data exists, rotate the source image before creating sub-sizes.
 	if ( ! empty( $image_meta['image_meta'] ) ) {
 		$rotated = $editor->maybe_exif_rotate();
@@ -457,7 +592,22 @@ function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) {
 				// TODO: Log errors.
 			} else {
 				// Save the size meta value.
-				$image_meta['sizes'][ $new_size_name ] = $new_size_meta;
+				if ( ! isset( $image_meta['sizes'][ $new_size_name ] ) ) {
+					$image_meta['sizes'][ $new_size_name ] = $new_size_meta;
+				} else {
+					// Remove any newly generated images that are larger than the primary mime type.
+					$new_size     = isset( $new_size_meta['filesize'] ) ? $new_size_meta['filesize'] : 0;
+					$primary_size = isset( $image_meta['sizes'][ $new_size_name ]['filesize'] ) ? $image_meta['sizes'][ $new_size_name ]['filesize'] : 0;
+
+					if ( $new_size && $primary_size && $new_size >= $primary_size ) {
+						wp_delete_file( dirname( $file ) . '/' . $new_size_meta['file'] );
+						continue;
+					}
+				}
+				if ( ! isset( $image_meta['sizes'][ $new_size_name ]['sources'] ) ) {
+					$image_meta['sizes'][ $new_size_name ]['sources'] = array();
+				}
+				$image_meta['sizes'][ $new_size_name ]['sources'][ $mime_type ] = _wp_get_sources_from_meta( $new_size_meta );
 				wp_update_attachment_metadata( $attachment_id, $image_meta );
 			}
 		}
@@ -466,7 +616,26 @@ function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) {
 		$created_sizes = $editor->multi_resize( $new_sizes );
 
 		if ( ! empty( $created_sizes ) ) {
-			$image_meta['sizes'] = array_merge( $image_meta['sizes'], $created_sizes );
+			foreach ( $created_sizes as $created_size_name => $created_size_meta ) {
+
+				// Primary mime type is set in 'sizes' array.
+				if ( ! isset( $image_meta['sizes'][ $created_size_name ] ) ) {
+					$image_meta['sizes'][ $created_size_name ] = $created_size_meta;
+				} else {
+					// Remove any newly generated images that are larger than the primary mime type.
+					$new_size     = isset( $created_size_meta['filesize'] ) ? $created_size_meta['filesize'] : 0;
+					$primary_size = isset( $image_meta['sizes'][ $created_size_name ]['filesize'] ) ? $image_meta['sizes'][ $created_size_name ]['filesize'] : 0;
+
+					if ( $new_size && $primary_size && $new_size >= $primary_size ) {
+						wp_delete_file( dirname( $file ) . '/' . $created_size_meta['file'] );
+						continue;
+					}
+				}
+				if ( ! isset( $image_meta['sizes'][ $created_size_name ]['sources'] ) ) {
+					$image_meta['sizes'][ $created_size_name ]['sources'] = array();
+				}
+				$image_meta['sizes'][ $created_size_name ]['sources'][ $mime_type ] = _wp_get_sources_from_meta( $new_size_meta );
+			}
 			wp_update_attachment_metadata( $attachment_id, $image_meta );
 		}
 	}
@@ -474,6 +643,125 @@ function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) {
 	return $image_meta;
 }
 
+/**
+ * Filters the list of image size objects that support secondary mime type output.
+ *
+ * @since 6.1.0
+ *
+ * @param array $sizes         Associative array of image sizes.
+ * @param int   $attachment_id Attachment ID.
+ * @return array $sizes Filtered $sizes with only those that support secondary mime type output.
+ */
+function _wp_filter_image_sizes_additional_mime_type_support( $sizes, $attachment_id ) {
+
+	// Include only the core sizes that do not rely on add_image_size(). Additional image sizes are opt-in.
+	$enabled_sizes = array(
+		'thumbnail'      => true,
+		'medium'         => true,
+		'medium_large'   => true,
+		'large'          => true,
+		'post-thumbnail' => true,
+	);
+
+	/**
+	 * Filter the sizes that support secondary mime type output. Developers can use this
+	 * to control the output of additional mime type sub-sized images.
+	 *
+	 * @since 6.1.0
+	 *
+	 * @param array $enabled_sizes Map of size names and whether they support secondary mime type output.
+	 * @param int   $attachment_id Attachment ID.
+	 */
+	$enabled_sizes = apply_filters( 'wp_image_sizes_with_additional_mime_type_support', $enabled_sizes, $attachment_id );
+
+	// Filter supported sizes to only include enabled sizes.
+	return array_intersect_key( $sizes, array_filter( $enabled_sizes ) );
+}
+
+/**
+ * Low-level function to create full-size images in additional mime types.
+ *
+ * Updates the image meta after each mime type image is created.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @param array  $new_mime_types Array defining what mime types 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_additional_mime_types( $new_mime_types, $file, $image_meta, $attachment_id ) {
+	$imagesize          = array(
+		$image_meta['width'],
+		$image_meta['height'],
+	);
+	$exif_meta          = isset( $image_meta['image_meta'] ) ? $image_meta['image_meta'] : null;
+	$original_file_size = isset( $image_meta['filesize'] ) ? $image_meta['filesize'] : wp_filesize( $file );
+
+	foreach ( $new_mime_types as $mime_type ) {
+		list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type );
+		if ( is_wp_error( $editor ) ) {
+			// The image cannot be edited.
+			continue;
+		}
+
+		$suffix    = _wp_get_image_suffix( $resized, $rotated );
+		$extension = wp_get_default_extension_for_mime_type( $mime_type );
+
+		$saved = $editor->save( $editor->generate_filename( $suffix, null, $extension ) );
+
+		if ( is_wp_error( $saved ) ) {
+			// TODO: Log errors.
+		} else {
+			// If the saved image is larger than the original, discard it.
+			$filesize = isset( $saved['filesize'] ) ? $saved['filesize'] : wp_filesize( $saved['path'] );
+			if ( $filesize && $original_file_size && $filesize > $original_file_size ) {
+				wp_delete_file( $saved['path'] );
+				continue;
+			}
+			$image_meta['sources'][ $mime_type ] = _wp_get_sources_from_meta( $saved );
+			wp_update_attachment_metadata( $attachment_id, $image_meta );
+		}
+	}
+
+	return $image_meta;
+}
+
+
+/**
+ * Check if an image belongs to an attachment.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @param string $filename     Full path to the image file.
+ * @param int   $attachment_id Attachment ID to check.
+ * @return bool True if the image belongs to the attachment, false otherwise.
+ */
+function _wp_image_belongs_to_attachment( $filename, $attachment_id ) {
+	$meta_data = wp_get_attachment_metadata( $attachment_id );
+
+	if ( ! isset( $image_meta['sizes'] ) ) {
+		return false;
+	}
+	$sizes = $image_meta['sizes'];
+	foreach ( $sizes as $size ) {
+		if ( $size['file'] === $filename ) {
+			return true;
+		}
+		if ( isset( $size['sources'] ) && is_array( $size['sources'] ) ) {
+			foreach ( $size['sources'] as $source ) {
+				if ( $source['file'] === $filename ) {
+					return true;
+				}
+			}
+		}
+	}
+	return false;
+}
+
 /**
  * Generate attachment meta data and create image sub-sizes for images.
  *
@@ -630,7 +918,7 @@ function wp_generate_attachment_metadata( $attachment_id, $file ) {
 					wp_update_attachment_metadata( $attachment_id, $metadata );
 
 					// Create sub-sizes saving the image meta after each.
-					$metadata = _wp_make_subsizes( $merged_sizes, $image_file, $metadata, $attachment_id );
+					$metadata = _wp_make_subsizes( $merged_sizes, $image_file, $metadata, $attachment_id, '' );
 				}
 			}
 		}
@@ -1157,3 +1445,97 @@ function _copy_image_file( $attachment_id ) {
 
 	return $dst_file;
 }
+
+/**
+ * Returns an array with the list of valid mime types that a specific mime type should be converted into.
+ * For example an `image/jpeg` should be converted into an `image/jpeg` and `image/webp`. The first type
+ * is considered the primary output type for this image.
+ *
+ * Called for each uploaded image to determine the list of mime types that should be converted into. Then,
+ * called again for each image size as they are generated to check if the image should be converted into the mime type
+ * for that size.
+ *
+ * @since 6.1.0
+ *
+ * @param int    $attachment_id  The attachment ID.
+ * @return array An array of valid mime types, where the key is the source file mime type and the list of mime types to
+ *               generate.
+ */
+function wp_upload_image_mime_transforms( $attachment_id ) {
+	$default_image_mime_transforms = array(
+		'image/jpeg' => array( 'image/jpeg', 'image/webp' ),
+		'image/webp' => array( 'image/webp', 'image/jpeg' ),
+	);
+	$image_mime_transforms         = $default_image_mime_transforms;
+
+	/**
+	 * Filter the output mime types for a given input mime type and image size.
+	 *
+	 * @since 6.1.0
+	 *
+	 * @param array  $image_mime_transforms A map with the valid mime transforms where the key is the source file mime type
+	 *                                      and the value is one or more mime file types to generate.
+	 * @param int    $attachment_id         The ID of the attachment where the hook was dispatched.
+	 */
+	$image_mime_transforms = apply_filters( 'wp_upload_image_mime_transforms', $image_mime_transforms, $attachment_id );
+
+	if ( ! is_array( $image_mime_transforms ) ) {
+		return $default_image_mime_transforms;
+	}
+
+	return array_map(
+		function( $transforms_list ) {
+			return (array) $transforms_list;
+		},
+		$image_mime_transforms
+	);
+}
+
+/**
+ * Extract the primary and additional mime output types for an image from the $image_mime_transforms.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @param string $file          Full path to the image file.
+ * @param int    $attachment_id Attachment ID to process.
+ * @return array An array with two entries, the primary mime type and the list of additional mime types.
+ */
+function _wp_get_primary_and_additional_mime_types( $file, $attachment_id ) {
+	$image_mime_transforms = wp_upload_image_mime_transforms( $attachment_id );
+	$original_mime_type    = wp_get_image_mime( $file );
+	$output_mime_types     = isset( $image_mime_transforms[ $original_mime_type ] ) ? $image_mime_transforms[ $original_mime_type ] : array( $original_mime_type );
+
+	// Exclude any output mime types that the system doesn't support.
+	$output_mime_types = array_values(
+		array_filter(
+			$output_mime_types,
+			function( $mime_type ) {
+				return wp_image_editor_supports(
+					array(
+						'mime_type' => $mime_type,
+					)
+				);
+			}
+		)
+	);
+
+	// Handle an empty value for $output_mime_types: only output the original type.
+	if ( empty( $output_mime_types ) ) {
+		return array( $original_mime_type, array() );
+	}
+
+	// Use original mime type as primary mime type, or alternatively the first one.
+	$primary_mime_type_key = array_search( $original_mime_type, $output_mime_types, true );
+	if ( false === $primary_mime_type_key ) {
+		$primary_mime_type_key = 0;
+	}
+	// Split output mime types into primary mime type and additional mime types.
+	$additional_mime_types     = $output_mime_types;
+	list( $primary_mime_type ) = array_splice( $additional_mime_types, $primary_mime_type_key, 1 );
+
+	return array(
+		$primary_mime_type,
+		$additional_mime_types,
+	);
+}
diff --git src/wp-includes/class-wp-image-editor.php src/wp-includes/class-wp-image-editor.php
index caa3092d36..ccf43402a6 100644
--- src/wp-includes/class-wp-image-editor.php
+++ src/wp-includes/class-wp-image-editor.php
@@ -334,6 +334,11 @@ abstract class WP_Image_Editor {
 	protected function get_output_format( $filename = null, $mime_type = null ) {
 		$new_ext = null;
 
+		// If no mime type is passed but output mime type is set, use that.
+		if ( ! $mime_type && ! empty( $this->output_mime_type ) ) {
+			$mime_type = $this->output_mime_type;
+		}
+
 		// By default, assume specified type takes priority.
 		if ( $mime_type ) {
 			$new_ext = $this->get_extension( $mime_type );
@@ -425,18 +430,25 @@ abstract class WP_Image_Editor {
 	}
 
 	/**
-	 * Builds an output filename based on current file, and adding proper suffix
+	 * Builds an output filename based on current file, and adding proper suffix.
 	 *
 	 * @since 3.5.0
-	 *
-	 * @param string $suffix
-	 * @param string $dest_path
-	 * @param string $extension
-	 * @return string filename
+	 * @since 6.1.0 Skips adding a suffix when set to an empty string. When the
+	 *              file extension being generated doesn't match the image file extension,
+	 *              add the extension to the suffix
+	 *
+	 * @param string $suffix    Optional. Suffix to add to the filename. The default null
+	 *                          will result in a 'widthxheight' suffix. Passing
+	 *                          an empty string will result in no suffix.
+	 * @param string $dest_path Optional. The path to save the file to. The default null
+	 *                          will use the image file path.
+	 * @param string $extension Optional. The file extension to use. The default null
+	 *                          will use the image file extension.
+	 * @return string filename The generated file name.
 	 */
 	public function generate_filename( $suffix = null, $dest_path = null, $extension = null ) {
 		// $suffix will be appended to the destination filename, just before the extension.
-		if ( ! $suffix ) {
+		if ( null === $suffix ) {
 			$suffix = $this->get_suffix();
 		}
 
@@ -457,7 +469,21 @@ abstract class WP_Image_Editor {
 			}
 		}
 
-		return trailingslashit( $dir ) . "{$name}-{$suffix}.{$new_ext}";
+		if ( empty( $suffix ) ) {
+			$suffix = '';
+		} else {
+			$suffix = "-{$suffix}";
+		}
+
+		// When the file extension being generated doesn't match the image file extension,
+		// add the extension to the suffix to ensure a unique file name. Prevents
+		// name conflicts when a single image type can have multiple extensions,
+		// eg. .jpg, .jpeg and .jpe are all valid JPEG extensions.
+		if ( ! empty( $extension ) && $extension !== $ext ) {
+			$suffix .= "-{$ext}";
+		}
+
+		return trailingslashit( $dir ) . "{$name}{$suffix}.{$new_ext}";
 	}
 
 	/**
@@ -637,5 +663,28 @@ abstract class WP_Image_Editor {
 
 		return wp_get_default_extension_for_mime_type( $mime_type );
 	}
-}
 
+	/**
+	 * Set the editor output mime type, useful when outputting alternate mime types.
+	 *
+	 * Track that the mime type is set with the mime type set flag.
+	 *
+	 * @since 6.1.0
+	 *
+	 * @param string $output_mime_type The mime type to set.
+	 */
+	public function set_output_mime_type( $output_mime_type ) {
+		$this->output_mime_type = $output_mime_type;
+	}
+
+	/**
+	 * Reset the mime type to the original file mime type.
+	 *
+	 * Reset the mime type set flag.
+	 *
+	 * @since 6.1.0
+	 */
+	public function reset_output_mime_type() {
+		$this->output_mime_type = $this->mime_type;
+	}
+}
diff --git src/wp-includes/media.php src/wp-includes/media.php
index aaf811d2d0..51c49f83d2 100644
--- src/wp-includes/media.php
+++ src/wp-includes/media.php
@@ -1852,6 +1852,11 @@ function wp_filter_content_tags( $content, $context = null ) {
 				$filtered_image = wp_img_tag_add_decoding_attr( $filtered_image, $context );
 			}
 
+			// Use alternate mime types when specified and available.
+			if ( $attachment_id > 0 && _wp_in_front_end_context() ) {
+				$filtered_image = wp_image_use_alternate_mime_types( $filtered_image, $context, $attachment_id );
+			}
+
 			/**
 			 * Filters an img tag within the content for a given context.
 			 *
@@ -1898,6 +1903,117 @@ function wp_filter_content_tags( $content, $context = null ) {
 	return $content;
 }
 
+/**
+ * Use alternate mime type images in the front end content output when available.
+ *
+ * @since 6.1.0
+ *
+ * @param string $image         The HTML `img` tag where the attribute should be added.
+ * @param string $context       Additional context to pass to the filters.
+ * @param int    $attachment_id The attachment ID.
+ * @return string Converted `img` tag with `loading` attribute added.
+ */
+function wp_image_use_alternate_mime_types( $image, $context, $attachment_id ) {
+	$metadata = wp_get_attachment_metadata( $attachment_id );
+	if ( empty( $metadata['file'] ) ) {
+		return $image;
+	}
+
+	// Only alter images with a `sources` attribute
+	if ( empty( $metadata['sources'] ) ) {
+		return $image;
+	};
+
+	$target_mimes = array( 'image/webp', 'image/jpeg' );
+
+	/**
+	 * Filter the content image mime type output selection and order.
+	 *
+	 * When outputting images in the content, the first mime type available will be used.
+	 *
+	 * @since 6.1.0
+	 *
+	 * @param array  $target_mimes  The image output mime type and order. Default is array( 'image/webp', 'image/jpeg' ).
+	 * @param int    $attachment_id The attachment ID.
+	 * @param string $context       Additional context to pass to the filters.
+	 * @return array The filtered output mime type and order. Return an empty array to skip mime type substitution.
+	 */
+	$target_mimes = apply_filters( 'wp_content_image_mimes', $target_mimes, $attachment_id, $context );
+
+	if ( false === $target_mimes ) {
+		return $image;
+	}
+
+	// Find the appropriate size for the provided URL in the first available mime type.
+	foreach ( $target_mimes as $target_mime ) {
+		// Handle full size image replacement.
+		if ( ! empty( $metadata['sources'][ $target_mime ]['file'] ) ) {
+			$src_filename = wp_basename( $metadata['file'] );
+
+			// This is the same MIME type as the original, so the entire $target_mime can be skipped.
+			// Since it is already the preferred MIME type, the entire loop can be cancelled.
+			if ( $metadata['sources'][ $target_mime ]['file'] === $src_filename ) {
+				break;
+			}
+
+			$image = str_replace( $src_filename, $metadata['sources'][ $target_mime ]['file'], $image );
+
+			// The full size was replaced, so unset this entirely here so that in the next iteration it is no longer
+			// considered, simply for a small performance optimization.
+			unset( $metadata['sources'] );
+		}
+
+		// Go through each image size and replace with the first available mime type version.
+		foreach ( $metadata['sizes'] as $name => $size_data ) {
+			// Check if size has an original file.
+			if ( empty( $size_data['file'] ) ) {
+				continue;
+			}
+
+			// Check if size has a source in the desired mime type.
+			if ( empty( $size_data['sources'][ $target_mime ]['file'] ) ) {
+				continue;
+			}
+
+			$src_filename = wp_basename( $size_data['file'] );
+
+			// This is the same MIME type as the original, so the entire $target_mime can be skipped.
+			// Since it is already the preferred MIME type, the entire loop can be cancelled.
+			if ( $size_data['sources'][ $target_mime ]['file'] === $src_filename ) {
+				break 2;
+			}
+
+			// Found a match, replace with the new filename.
+			$image = str_replace( $src_filename, $size_data['sources'][ $target_mime ]['file'], $image );
+
+			// This size was replaced, so unset this entirely here so that in the next iteration it is no longer
+			// considered, simply for a small performance optimization.
+			unset( $metadata['sizes'][ $name ] );
+		}
+	}
+	return $image;
+}
+
+/**
+ * Check if execution is currently in the front end content context, outside of <head>.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @return bool True if in the front end content context, false otherwise.
+ */
+function _wp_in_front_end_context() {
+	global $wp_query;
+
+	// Check if this request is generally outside (or before) any frontend context.
+	if ( ! isset( $wp_query ) || defined( 'XMLRPC_REQUEST' ) || defined( 'REST_REQUEST' ) || is_feed() ) {
+		return false;
+	}
+
+	// Check if we're anywhere before the 'wp_head' action has completed.
+	return did_action( 'template_redirect' ) && ! doing_action( 'wp_head' );
+}
+
 /**
  * Adds `loading` attribute to an `img` HTML tag.
  *
diff --git src/wp-includes/post.php src/wp-includes/post.php
index 9689e008ea..c7dcc13b8d 100644
--- src/wp-includes/post.php
+++ src/wp-includes/post.php
@@ -6481,13 +6481,28 @@ function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) {
 		$intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
 
 		foreach ( $meta['sizes'] as $size => $sizeinfo ) {
-			$intermediate_file = str_replace( wp_basename( $file ), $sizeinfo['file'], $file );
 
-			if ( ! empty( $intermediate_file ) ) {
-				$intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
+			// Check for alternate size mime types in the sizeinfo['sources'] array to delete.
+			if ( isset( $sizeinfo['sources'] ) && is_array( $sizeinfo['sources'] ) ) {
+				foreach ( $sizeinfo['sources'] as $mime => $properties ) {
+					$intermediate_file = str_replace( wp_basename( $file ), $properties['file'], $file );
+					if ( ! empty( $intermediate_file ) ) {
+						$intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
+						if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
+							$deleted = false;
+						}
+					}
+				}
+			} else {
+				// Otherwise, delete files from the sizeinfo data.
+				$intermediate_file = str_replace( wp_basename( $file ), $sizeinfo['file'], $file );
 
-				if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
-					$deleted = false;
+				if ( ! empty( $intermediate_file ) ) {
+					$intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
+
+					if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
+						$deleted = false;
+					}
 				}
 			}
 		}
@@ -6509,26 +6524,60 @@ function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) {
 		}
 	}
 
+	// Delete the full size images from 'sources' if available, or the root file.
+	if ( isset( $meta['sources'] ) && is_array( $meta['sources'] ) ) {
+		$sources          = $meta['sources'];
+		$intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
+		foreach ( $sources as $mime => $properties ) {
+			if ( ! is_array( $properties ) || empty( $properties['file'] ) ) {
+				continue;
+			}
+			$intermediate_file = str_replace( wp_basename( $file ), $properties['file'], $file );
+			if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
+				$deleted = false;
+			}
+		}
+	} else {
+		if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
+			$deleted = false;
+		}
+	}
+
 	if ( is_array( $backup_sizes ) ) {
-		$del_dir = path_join( $uploadpath['basedir'], dirname( $meta['file'] ) );
 
+		$del_dir = path_join( $uploadpath['basedir'], dirname( $meta['file'] ) );
+		// Delete the root (edited) file which was not deleted above.
+		if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
+			$deleted = false;
+		}
 		foreach ( $backup_sizes as $size ) {
-			$del_file = path_join( dirname( $meta['file'] ), $size['file'] );
-
-			if ( ! empty( $del_file ) ) {
-				$del_file = path_join( $uploadpath['basedir'], $del_file );
+			// Delete files from 'sources' data if available, otherwise from 'sizes' data.
+			if ( isset( $meta['sources'] ) && is_array( $meta['sources'] ) ) {
+				// Delete any backup images stored in the 'sources' array.
+				if ( isset( $size['sources'] ) && is_array( $size['sources'] ) ) {
+					foreach ( $size['sources'] as $mime => $properties ) {
+						$del_file = path_join( dirname( $meta['file'] ), $properties['file'] );
+						if ( ! empty( $del_file ) ) {
+							$del_file = path_join( $uploadpath['basedir'], $del_file );
+							if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
+								$deleted = false;
+							}
+						}
+					}
+				}
+			} else {
+				$del_file = path_join( dirname( $meta['file'] ), $size['file'] );
 
-				if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
-					$deleted = false;
+				if ( ! empty( $del_file ) ) {
+					$del_file = path_join( $uploadpath['basedir'], $del_file );
+					if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
+						$deleted = false;
+					}
 				}
 			}
 		}
 	}
 
-	if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
-		$deleted = false;
-	}
-
 	return $deleted;
 }
 
diff --git tests/phpunit/data/images/test-image-rotated-90ccw.jpg tests/phpunit/data/images/test-image-rotated-90ccw.jpg
new file mode 100644
index 0000000000..b579b7f9ab
Binary files /dev/null and tests/phpunit/data/images/test-image-rotated-90ccw.jpg differ
diff --git tests/phpunit/data/images/test-image-rotated-90cw.webp tests/phpunit/data/images/test-image-rotated-90cw.webp
new file mode 100644
index 0000000000..82e77bed08
Binary files /dev/null and tests/phpunit/data/images/test-image-rotated-90cw.webp differ
diff --git tests/phpunit/data/images/test-image.jpeg tests/phpunit/data/images/test-image.jpeg
new file mode 100644
index 0000000000..534aac1d6b
Binary files /dev/null and tests/phpunit/data/images/test-image.jpeg differ
diff --git tests/phpunit/tests/image/editor.php tests/phpunit/tests/image/editor.php
index 487dad0664..c051ffec2b 100644
--- tests/phpunit/tests/image/editor.php
+++ tests/phpunit/tests/image/editor.php
@@ -131,6 +131,7 @@ class Tests_Image_Editor extends WP_Image_UnitTestCase {
 		$this->assertSame( 86, $editor->get_quality(), 'Output image format is WEBP. Quality setting for it should be 86.' );
 
 		// Removing PNG to WEBP conversion on save. Quality setting should reset to the default.
+		$editor->reset_output_mime_type();
 		remove_filter( 'image_editor_output_format', array( $this, 'image_editor_output_formats' ) );
 		$editor->save();
 		$this->assertSame( 82, $editor->get_quality(), 'After removing image conversion quality setting should reset to the default of 82.' );
@@ -154,6 +155,7 @@ class Tests_Image_Editor extends WP_Image_UnitTestCase {
 		$this->assertSame( 42, $editor->get_quality(), 'Image conversion from JPEG to WEBP. Filtered WEBP quality shoild be 42.' );
 
 		// After removing the conversion the quality setting should reset to the filtered value for the original image type, JPEG.
+		$editor->reset_output_mime_type();
 		remove_filter( 'image_editor_output_format', array( $this, 'image_editor_output_formats' ) );
 		$editor->save();
 		$this->assertSame(
@@ -226,10 +228,10 @@ class Tests_Image_Editor extends WP_Image_UnitTestCase {
 		$this->assertSame( trailingslashit( realpath( get_temp_dir() ) ), trailingslashit( realpath( dirname( $editor->generate_filename( null, get_temp_dir() ) ) ) ) );
 
 		// Test with a suffix only.
-		$this->assertSame( 'canola-100x50.png', wp_basename( $editor->generate_filename( null, null, 'png' ) ) );
+		$this->assertSame( 'canola-100x50-jpg.png', wp_basename( $editor->generate_filename( null, null, 'png' ) ) );
 
 		// Combo!
-		$this->assertSame( trailingslashit( realpath( get_temp_dir() ) ) . 'canola-new.png', $editor->generate_filename( 'new', realpath( get_temp_dir() ), 'png' ) );
+		$this->assertSame( trailingslashit( realpath( get_temp_dir() ) ) . 'canola-new-jpg.png', $editor->generate_filename( 'new', realpath( get_temp_dir() ), 'png' ) );
 
 		// Test with a stream destination.
 		$this->assertSame( 'file://testing/path/canola-100x50.jpg', $editor->generate_filename( null, 'file://testing/path' ) );
@@ -362,4 +364,404 @@ class Tests_Image_Editor extends WP_Image_UnitTestCase {
 		);
 	}
 
+	/**
+	 * Test creating  the original image mime type when the image is uploaded.
+	 *
+	 * @ticket 55443
+	 *
+	 * @dataProvider provider_image_with_default_behaviors_during_upload
+	 */
+	public function it_should_create_the_original_image_mime_type_when_the_image_is_uploaded( $file_location, $expected_mime, $targeted_mime ) {
+		$attachment_id = $this->factory->attachment->create_upload_object( $file_location );
+
+		$metadata = wp_get_attachment_metadata( $attachment_id );
+
+		$this->assertIsArray( $metadata );
+		foreach ( $metadata['sizes'] as $size_name => $properties ) {
+			$this->assertArrayHasKey( 'sources', $properties );
+			$this->assertIsArray( $properties['sources'] );
+			$this->assertArrayHasKey( $expected_mime, $properties['sources'] );
+			$this->assertArrayHasKey( 'filesize', $properties['sources'][ $expected_mime ] );
+			$this->assertArrayHasKey( 'file', $properties['sources'][ $expected_mime ] );
+			$this->assertArrayHasKey( $targeted_mime, $properties['sources'] );
+			$this->assertArrayHasKey( 'filesize', $properties['sources'][ $targeted_mime ] );
+			$this->assertArrayHasKey( 'file', $properties['sources'][ $targeted_mime ] );
+		}
+	}
+
+	/**
+	 * Data provider for it_should_create_the_original_image_mime_type_when_the_image_is_uploaded.
+	 */
+	public function provider_image_with_default_behaviors_during_upload() {
+		yield 'JPEG image' => array(
+			DIR_TESTDATA . '/images/test-image.jpg',
+			'image/jpeg',
+			'image/webp',
+		);
+
+		yield 'WebP image' => array(
+			DIR_TESTDATA . '/images/webp-lossy.webp',
+			'image/webp',
+			'image/jpeg',
+		);
+	}
+
+	/**
+	 * Test Do not create the sources property if no transform is provided.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_not_create_the_sources_property_if_no_transform_is_provided() {
+		add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
+		$attachment_id = $this->factory->attachment->create_upload_object(
+			DIR_TESTDATA . '/images/test-image.jpg'
+		);
+
+		$metadata = wp_get_attachment_metadata( $attachment_id );
+
+		$this->assertIsArray( $metadata );
+		foreach ( $metadata['sizes'] as $size_name => $properties ) {
+			$this->assertArrayNotHasKey( 'sources', $properties );
+		}
+	}
+
+	/**
+	 * Test creating the sources property when no transform is available.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_create_the_sources_property_when_no_transform_is_available() {
+		add_filter(
+			'wp_upload_image_mime_transforms',
+			function () {
+				return array( 'image/jpeg' => array() );
+			}
+		);
+
+		$attachment_id = $this->factory->attachment->create_upload_object(
+			DIR_TESTDATA . '/images/test-image.jpg'
+		);
+
+		$metadata = wp_get_attachment_metadata( $attachment_id );
+
+		$this->assertIsArray( $metadata );
+		foreach ( $metadata['sizes'] as $size_name => $properties ) {
+			$this->assertArrayHasKey( 'sources', $properties );
+			$this->assertIsArray( $properties['sources'] );
+			$this->assertArrayHasKey( 'image/jpeg', $properties['sources'] );
+			$this->assertArrayHasKey( 'filesize', $properties['sources']['image/jpeg'] );
+			$this->assertArrayHasKey( 'file', $properties['sources']['image/jpeg'] );
+			$this->assertArrayNotHasKey( 'image/webp', $properties['sources'] );
+		}
+	}
+
+	/**
+	 * Test not creating the sources property if the mime is not specified on the transforms images.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_not_create_the_sources_property_if_the_mime_is_not_specified_on_the_transforms_images() {
+		add_filter(
+			'wp_upload_image_mime_transforms',
+			function () {
+				return array( 'image/jpeg' => array() );
+			}
+		);
+
+		$attachment_id = $this->factory->attachment->create_upload_object(
+			DIR_TESTDATA . '/images/webp-lossy.webp'
+		);
+
+		$metadata = wp_get_attachment_metadata( $attachment_id );
+
+		$this->assertIsArray( $metadata );
+		foreach ( $metadata['sizes'] as $size_name => $properties ) {
+			$this->assertArrayNotHasKey( 'sources', $properties );
+		}
+	}
+
+
+	/**
+	 * Test creating a WebP version with all the required properties.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_create_a_webp_version_with_all_the_required_properties() {
+		$attachment_id = $this->factory->attachment->create_upload_object(
+			DIR_TESTDATA . '/images/test-image.jpg'
+		);
+
+		$metadata = wp_get_attachment_metadata( $attachment_id );
+		$this->assertArrayHasKey( 'sources', $metadata['sizes']['thumbnail'] );
+		$this->assertArrayHasKey( 'image/jpeg', $metadata['sizes']['thumbnail']['sources'] );
+		$this->assertArrayHasKey( 'filesize', $metadata['sizes']['thumbnail']['sources']['image/jpeg'] );
+		$this->assertArrayHasKey( 'file', $metadata['sizes']['thumbnail']['sources']['image/jpeg'] );
+		$this->assertArrayHasKey( 'image/webp', $metadata['sizes']['thumbnail']['sources'] );
+		$this->assertArrayHasKey( 'filesize', $metadata['sizes']['thumbnail']['sources']['image/webp'] );
+		$this->assertArrayHasKey( 'file', $metadata['sizes']['thumbnail']['sources']['image/webp'] );
+		$this->assertStringEndsNotWith( '.jpeg', $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] );
+		$this->assertStringEndsWith( '.webp', $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] );
+	}
+
+	/**
+	 * Test removing `scaled` suffix from the generated filename.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_remove_scaled_suffix_from_the_generated_filename() {
+		// The leafs image is 1080 pixels wide with this filter we ensure a -scaled version is created.
+		add_filter(
+			'big_image_size_threshold',
+			function () {
+				return 850;
+			}
+		);
+
+		$attachment_id = $this->factory->attachment->create_upload_object(
+			DIR_TESTDATA . '/images/test-image.jpg'
+		);
+		$metadata      = wp_get_attachment_metadata( $attachment_id );
+		$this->assertStringEndsWith( '-scaled.jpg', get_attached_file( $attachment_id ) );
+		$this->assertArrayHasKey( 'image/webp', $metadata['sizes']['medium']['sources'] );
+		$this->assertStringEndsNotWith( '-scaled.webp', $metadata['sizes']['medium']['sources']['image/webp']['file'] );
+		$this->assertStringEndsWith( '-300x200.webp', $metadata['sizes']['medium']['sources']['image/webp']['file'] );
+	}
+
+	/**
+	 * Test removing the generated webp images when the attachment is deleted.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_remove_the_generated_webp_images_when_the_attachment_is_deleted() {
+		// Make sure no editor is available.
+		$attachment_id = $this->factory->attachment->create_upload_object(
+			DIR_TESTDATA . '/images/test-image.jpg'
+		);
+
+		$file    = get_attached_file( $attachment_id, true );
+		$dirname = pathinfo( $file, PATHINFO_DIRNAME );
+
+		$this->assertIsString( $file );
+		$this->assertFileExists( $file );
+
+		$metadata = wp_get_attachment_metadata( $attachment_id );
+		$sizes    = array( 'thumbnail', 'medium' );
+
+		foreach ( $sizes as $size_name ) {
+			$this->assertArrayHasKey( 'image/webp', $metadata['sizes'][ $size_name ]['sources'] );
+			$this->assertArrayHasKey( 'file', $metadata['sizes'][ $size_name ]['sources']['image/webp'] );
+			$this->assertFileExists(
+				path_join( $dirname, $metadata['sizes'][ $size_name ]['sources']['image/webp']['file'] )
+			);
+		}
+
+		wp_delete_attachment( $attachment_id );
+
+		foreach ( $sizes as $size_name ) {
+			$this->assertFileDoesNotExist(
+				path_join( $dirname, $metadata['sizes'][ $size_name ]['sources']['image/webp']['file'] )
+			);
+		}
+	}
+
+	/**
+	 * Test removing the attached WebP version if the attachment is force deleted but empty trash day is not defined.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_remove_the_attached_webp_version_if_the_attachment_is_force_deleted_but_empty_trash_day_is_not_defined() {
+		// Make sure no editor is available.
+		$attachment_id = $this->factory->attachment->create_upload_object(
+			DIR_TESTDATA . '/images/test-image.jpg'
+		);
+
+		$file    = get_attached_file( $attachment_id, true );
+		$dirname = pathinfo( $file, PATHINFO_DIRNAME );
+
+		$this->assertIsString( $file );
+		$this->assertFileExists( $file );
+
+		$metadata = wp_get_attachment_metadata( $attachment_id );
+
+		$this->assertFileExists(
+			path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
+		);
+
+		wp_delete_attachment( $attachment_id, true );
+
+		$this->assertFileDoesNotExist(
+			path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
+		);
+	}
+
+	/**
+	 * Test removing the WebP version of the image if the image is force deleted and empty trash days is set to zero.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_remove_the_webp_version_of_the_image_if_the_image_is_force_deleted_and_empty_trash_days_is_set_to_zero() {
+		// Make sure no editor is available.
+		$attachment_id = $this->factory->attachment->create_upload_object(
+			DIR_TESTDATA . '/images/test-image.jpg'
+		);
+
+		$file    = get_attached_file( $attachment_id, true );
+		$dirname = pathinfo( $file, PATHINFO_DIRNAME );
+
+		$this->assertIsString( $file );
+		$this->assertFileExists( $file );
+
+		$metadata = wp_get_attachment_metadata( $attachment_id );
+
+		$this->assertFileExists(
+			path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
+		);
+
+		define( 'EMPTY_TRASH_DAYS', 0 );
+
+		wp_delete_attachment( $attachment_id, true );
+
+		$this->assertFileDoesNotExist(
+			path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
+		);
+	}
+
+	/**
+	 * Test avoiding the change of URLs of images that are not part of the media library.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_avoid_the_change_of_urls_of_images_that_are_not_part_of_the_media_library() {
+		$paragraph = '<p>Donec accumsan, sapien et <img src="https://ia600200.us.archive.org/16/items/SPD-SLRSY-1867/hubblesite_2001_06.jpg">, id commodo nisi sapien et est. Mauris nisl odio, iaculis vitae pellentesque nec.</p>';
+
+		$this->assertSame( $paragraph, webp_uploads_update_image_references( $paragraph ) );
+	}
+
+	/**
+	 * Test avoiding replacing not existing attachment IDs.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_avoid_replacing_not_existing_attachment_i_ds() {
+		$paragraph = '<p>Donec accumsan, sapien et <img class="wp-image-0" src="https://ia600200.us.archive.org/16/items/SPD-SLRSY-1867/hubblesite_2001_06.jpg">, id commodo nisi sapien et est. Mauris nisl odio, iaculis vitae pellentesque nec.</p>';
+
+		$this->assertSame( $paragraph, webp_uploads_update_image_references( $paragraph ) );
+	}
+
+	/**
+	 * Test preventing replacing a WebP image.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_test_preventing_replacing_a_webp_image() {
+		$attachment_id = $this->factory->attachment->create_upload_object(
+			DIR_TESTDATA . '/images/webp-lossy.webp'
+		);
+
+		$tag = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) );
+
+		$this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
+	}
+
+	/**
+	 * Test preventing replacing a jpg image if the image does not have the target class name.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_test_preventing_replacing_a_jpg_image_if_the_image_does_not_have_the_target_class_name() {
+		$attachment_id = $this->factory->attachment->create_upload_object(
+			DIR_TESTDATA . '/images/test-image.jpg'
+		);
+
+		$tag = wp_get_attachment_image( $attachment_id, 'medium' );
+
+		$this->assertSame( $tag, webp_uploads_update_image_references( $tag ) );
+	}
+
+	/**
+	 * Test replacing the references to a JPG image to a WebP version.
+	 *
+	 * @dataProvider provider_replace_images_with_different_extensions
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_replace_the_references_to_a_jpg_image_to_a_webp_version( $image_path ) {
+		$attachment_id = $this->factory->attachment->create_upload_object( $image_path );
+
+		$tag          = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) );
+		$expected_tag = $tag;
+		$metadata     = wp_get_attachment_metadata( $attachment_id );
+		foreach ( $metadata['sizes'] as $size => $properties ) {
+			$expected_tag = str_replace( $properties['sources']['image/jpeg']['file'], $properties['sources']['image/webp']['file'], $expected_tag );
+		}
+
+		$this->assertNotEmpty( $expected_tag );
+		$this->assertNotSame( $tag, $expected_tag );
+		$this->assertSame( $expected_tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
+	}
+
+	public function provider_replace_images_with_different_extensions() {
+		yield 'An image with a .jpg extension' => array( DIR_TESTDATA . '/images/test-image.jpg' );
+		yield 'An image with a .jpeg extension' => array( DIR_TESTDATA . '/images/test-image.jpeg' );
+	}
+
+	/**
+	 * Test the full image size from the original mime type.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_contain_the_full_image_size_from_the_original_mime() {
+		$attachment_id = $this->factory->attachment->create_upload_object(
+			DIR_TESTDATA . '/images/test-image.jpg'
+		);
+
+		$tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
+
+		$expected = array(
+			'ext'  => 'jpg',
+			'type' => 'image/jpeg',
+		);
+		$this->assertSame( $expected, wp_check_filetype( get_attached_file( $attachment_id ) ) );
+		$this->assertContains( wp_basename( get_attached_file( $attachment_id ) ), webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
+	}
+
+	/**
+	 * Test preventing replacing an image with no available sources.
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_prevent_replacing_an_image_with_no_available_sources() {
+		add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
+		$attachment_id = $this->factory->attachment->create_upload_object( DIR_TESTDATA . '/images/test-image.jpg' );
+
+		$tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
+		$this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
+	}
+
+	/**
+	 * Test preventing update not supported images with no available sources.
+	 *
+	 * @dataProvider provider_it_should_prevent_update_not_supported_images_with_no_available_sources
+	 *
+	 * @ticket 55443
+	 */
+	public function it_should_prevent_update_not_supported_images_with_no_available_sources( $image_path ) {
+		$attachment_id = $this->factory->attachment->create_upload_object( $image_path );
+
+		$this->assertIsNumeric( $attachment_id );
+		$tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
+
+		$this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
+	}
+
+	/**
+	 * Data provider for it_should_prevent_update_not_supported_images_with_no_available_sources.
+	 */
+	public function provider_it_should_prevent_update_not_supported_images_with_no_available_sources() {
+		yield 'PNG image' => array( DIR_TESTDATA . '/images/test-image.png' );
+		yield 'GIFT image' => array( DIR_TESTDATA . '/images/test-image.gif' );
+	}
+
 }
diff --git tests/phpunit/tests/image/functions.php tests/phpunit/tests/image/functions.php
index 86d559145e..cb5a7971c9 100644
--- tests/phpunit/tests/image/functions.php
+++ tests/phpunit/tests/image/functions.php
@@ -639,6 +639,9 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 			$this->markTestSkipped( 'Rendering PDFs is not supported on this system.' );
 		}
 
+		// Use legacy JPEG output.
+		add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
 		$orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
 		$test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
 		copy( $orig_file, $test_file );
@@ -677,6 +680,12 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 					'height'    => 300,
 					'mime-type' => 'image/jpeg',
 					'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-232x300.jpg' ),
+					'sources'   => array(
+						'image/jpeg' => array(
+							'file'     => 'wordpress-gsoc-flyer-pdf-232x300.jpg',
+							'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-232x300.jpg' ),
+						),
+					),
 				),
 				'large'     => array(
 					'file'      => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
@@ -684,6 +693,12 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 					'height'    => 1024,
 					'mime-type' => 'image/jpeg',
 					'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
+					'sources'   => array(
+						'image/jpeg' => array(
+							'file'     => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
+							'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
+						),
+					),
 				),
 				'thumbnail' => array(
 					'file'      => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
@@ -691,6 +706,12 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 					'height'    => 150,
 					'mime-type' => 'image/jpeg',
 					'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
+					'sources'   => array(
+						'image/jpeg' => array(
+							'file'     => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
+							'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
+						),
+					),
 				),
 			),
 			'filesize' => wp_filesize( $test_file ),
@@ -702,6 +723,7 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 		foreach ( $metadata['sizes'] as $size ) {
 			unlink( $temp_dir . $size['file'] );
 		}
+		remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
 	}
 
 	/**
@@ -716,6 +738,9 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 
 		update_option( 'medium_crop', 1 );
 
+		// Use legacy JPEG output.
+		add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
 		$orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
 		$test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
 		copy( $orig_file, $test_file );
@@ -754,6 +779,12 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 					'height'    => 300,
 					'mime-type' => 'image/jpeg',
 					'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-300x300.jpg' ),
+					'sources'   => array(
+						'image/jpeg' => array(
+							'file'     => 'wordpress-gsoc-flyer-pdf-300x300.jpg',
+							'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-300x300.jpg' ),
+						),
+					),
 				),
 				'large'     => array(
 					'file'      => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
@@ -761,6 +792,13 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 					'height'    => 1024,
 					'mime-type' => 'image/jpeg',
 					'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
+					'sources'   => array(
+						'image/jpeg' => array(
+							'file'     => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
+							'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
+						),
+					),
+
 				),
 				'thumbnail' => array(
 					'file'      => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
@@ -768,6 +806,12 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 					'height'    => 150,
 					'mime-type' => 'image/jpeg',
 					'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
+					'sources'   => array(
+						'image/jpeg' => array(
+							'file'     => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
+							'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
+						),
+					),
 				),
 			),
 			'filesize' => wp_filesize( $test_file ),
@@ -779,6 +823,8 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 		foreach ( $metadata['sizes'] as $size ) {
 			unlink( $temp_dir . $size['file'] );
 		}
+		remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
 	}
 
 	/**
@@ -789,6 +835,9 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 			$this->markTestSkipped( 'Rendering PDFs is not supported on this system.' );
 		}
 
+		// Use legacy JPEG output.
+		add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
 		$orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
 		$test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
 		copy( $orig_file, $test_file );
@@ -821,6 +870,12 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 			'height'    => 100,
 			'mime-type' => 'image/jpeg',
 			'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-77x100.jpg' ),
+			'sources'   => array(
+				'image/jpeg' => array(
+					'file'     => 'wordpress-gsoc-flyer-pdf-77x100.jpg',
+					'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-77x100.jpg' ),
+				),
+			),
 		);
 
 		// Different environments produce slightly different filesize results.
@@ -836,6 +891,7 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 		foreach ( $metadata['sizes'] as $size ) {
 			unlink( $temp_dir . $size['file'] );
 		}
+		remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
 	}
 
 	public function filter_fallback_intermediate_image_sizes( $fallback_sizes, $metadata ) {
@@ -1026,4 +1082,431 @@ class Tests_Image_Functions extends WP_UnitTestCase {
 			),
 		);
 	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test_wp_upload_image_mime_transforms_generates_webp_and_jpeg_for_both_by_default() {
+		$result = wp_upload_image_mime_transforms( 42 );
+		$this->assertArrayHasKey( 'image/jpeg', $result );
+		$this->assertArrayHasKey( 'image/webp', $result );
+		$this->assertSameSets( array( 'image/jpeg', 'image/webp' ), $result['image/jpeg'] );
+		$this->assertSameSets( array( 'image/jpeg', 'image/webp' ), $result['image/webp'] );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test_wp_upload_image_mime_transforms_filter_always_use_webp_instead_of_jpeg() {
+		add_filter(
+			'wp_upload_image_mime_transforms',
+			function( $transforms ) {
+				// Ensure JPG only results in WebP files.
+				$transforms['image/jpeg'] = array( 'image/webp' );
+				// Unset WebP since it does not need any transformation in that case.
+				unset( $transforms['image/webp'] );
+				return $transforms;
+			}
+		);
+
+		$result = wp_upload_image_mime_transforms( 42 );
+		$this->assertArrayHasKey( 'image/jpeg', $result );
+		$this->assertArrayNotHasKey( 'image/webp', $result );
+		$this->assertSameSets( array( 'image/webp' ), $result['image/jpeg'] );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test_wp_upload_image_mime_transforms_filter_receives_parameters() {
+		$attachment_id = null;
+		add_filter(
+			'wp_upload_image_mime_transforms',
+			function( $transforms, $param1 ) use ( &$attachment_id ) {
+				$attachment_id = $param1;
+				return $transforms;
+			},
+			10,
+			2
+		);
+
+		wp_upload_image_mime_transforms( 23 );
+		$this->assertSame( 23, $attachment_id );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test_wp_upload_image_mime_transforms_filter_with_empty_array() {
+		add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+		$result = wp_upload_image_mime_transforms( 42 );
+		$this->assertSame( array(), $result );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test_wp_upload_image_mime_transforms_filter_with_invalid_usage() {
+		$default = wp_upload_image_mime_transforms( 42 );
+
+		add_filter( 'wp_upload_image_mime_transforms', '__return_false' );
+		$result = wp_upload_image_mime_transforms( 42 );
+		$this->assertSame( $default, $result );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test__wp_get_primary_and_additional_mime_types_default() {
+		$jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
+
+		list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
+		$this->assertSame( 'image/jpeg', $primary_mime_type );
+
+		// WebP may not be supported by the server, in which case it will be stripped from the results.
+		if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+			$this->assertSame( array( 'image/webp' ), $additional_mime_types );
+		} else {
+			$this->assertSame( array(), $additional_mime_types );
+		}
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test__wp_get_primary_and_additional_mime_types_prefer_original_mime() {
+		$jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
+
+		// Set 'image/jpeg' only as secondary output MIME type.
+		// Still, because it is the original, it should be chosen as primary over 'image/webp'.
+		add_filter(
+			'wp_upload_image_mime_transforms',
+			function( $transforms ) {
+				$transforms['image/jpeg'] = array( 'image/webp', 'image/jpeg' );
+				return $transforms;
+			}
+		);
+
+		list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
+		$this->assertSame( 'image/jpeg', $primary_mime_type );
+
+		// WebP may not be supported by the server, in which case it will be stripped from the results.
+		if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+			$this->assertSame( array( 'image/webp' ), $additional_mime_types );
+		} else {
+			$this->assertSame( array(), $additional_mime_types );
+		}
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test__wp_get_primary_and_additional_mime_types_use_original_mime_when_no_transformation_rules() {
+		$jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
+
+		// Strip all transformation rules.
+		add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
+		list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
+		$this->assertSame( 'image/jpeg', $primary_mime_type );
+		$this->assertSame( array(), $additional_mime_types );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test__wp_get_primary_and_additional_mime_types_different_output_mime() {
+		$jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
+
+		// Set 'image/webp' as the only output MIME type.
+		// In that case, JPEG is not generated at all, so WebP becomes the primary MIME type.
+		add_filter(
+			'wp_upload_image_mime_transforms',
+			function( $transforms ) {
+				$transforms['image/jpeg'] = array( 'image/webp' );
+				return $transforms;
+			}
+		);
+
+		list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
+
+		// WebP may not be supported by the server, in which case it will fall back to the original MIME type.
+		if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+			$this->assertSame( 'image/webp', $primary_mime_type );
+		} else {
+			$this->assertSame( 'image/jpeg', $primary_mime_type );
+		}
+
+		$this->assertSame( array(), $additional_mime_types );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test__wp_get_primary_and_additional_mime_types_different_output_mimes() {
+		$jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
+
+		// Set 'image/webp' and 'image/avif' as output MIME types.
+		// In that case, JPEG is not generated at all, with WebP being the primary MIME type and AVIF the secondary.
+		add_filter(
+			'wp_upload_image_mime_transforms',
+			function( $transforms ) {
+				$transforms['image/jpeg'] = array( 'image/webp', 'image/avif' );
+				return $transforms;
+			}
+		);
+
+		list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
+
+		// WebP may not be supported by the server, in which case it will fall back to the original MIME type.
+		if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+			$this->assertSame( 'image/webp', $primary_mime_type );
+		} else {
+			$this->assertSame( 'image/jpeg', $primary_mime_type );
+		}
+
+		// AVIF may not be supported by the server, in which case it will be stripped from the results.
+		if ( wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) {
+			$this->assertSame( array( 'image/avif' ), $additional_mime_types );
+		} else {
+			$this->assertSame( array(), $additional_mime_types );
+		}
+	}
+
+	/**
+	 * @ticket 55443
+	 * @dataProvider data__wp_filter_image_sizes_additional_mime_type_support
+	 */
+	public function test__wp_filter_image_sizes_additional_mime_type_support( $input_size_data, $filter_callback, $expected_size_names ) {
+		remove_all_filters( 'wp_image_sizes_with_additional_mime_type_support' );
+		if ( $filter_callback ) {
+			add_filter( 'wp_image_sizes_with_additional_mime_type_support', $filter_callback );
+		}
+
+		$expected_size_data = array_intersect_key( $input_size_data, array_flip( $expected_size_names ) );
+
+		$output_size_data = _wp_filter_image_sizes_additional_mime_type_support( $input_size_data, 42 );
+		$this->assertEqualSetsWithIndex( $expected_size_data, $output_size_data );
+	}
+
+	public function data__wp_filter_image_sizes_additional_mime_type_support() {
+		$thumbnail_data    = array(
+			'width'  => 150,
+			'height' => 150,
+			'crop'   => true,
+		);
+		$medium_data       = array(
+			'width'  => 300,
+			'height' => 300,
+			'crop'   => false,
+		);
+		$medium_large_data = array(
+			'width'  => 768,
+			'height' => 0,
+			'crop'   => false,
+		);
+		$large_data        = array(
+			'width'  => 1024,
+			'height' => 1024,
+			'crop'   => false,
+		);
+		$custom_data       = array(
+			'width'  => 512,
+			'height' => 512,
+			'crop'   => true,
+		);
+
+		return array(
+			array(
+				array(
+					'thumbnail'    => $thumbnail_data,
+					'medium'       => $medium_data,
+					'medium_large' => $medium_large_data,
+					'large'        => $large_data,
+				),
+				null,
+				array( 'thumbnail', 'medium', 'medium_large', 'large' ),
+			),
+			array(
+				array(
+					'thumbnail' => $thumbnail_data,
+					'medium'    => $medium_data,
+					'custom'    => $custom_data,
+				),
+				null,
+				array( 'thumbnail', 'medium' ),
+			),
+			array(
+				array(
+					'thumbnail'    => $thumbnail_data,
+					'medium'       => $medium_data,
+					'medium_large' => $medium_large_data,
+					'large'        => $large_data,
+				),
+				function( $enabled_sizes ) {
+					unset( $enabled_sizes['medium_large'], $enabled_sizes['large'] );
+					return $enabled_sizes;
+				},
+				array( 'thumbnail', 'medium' ),
+			),
+			array(
+				array(
+					'thumbnail'    => $thumbnail_data,
+					'medium'       => $medium_data,
+					'medium_large' => $medium_large_data,
+					'large'        => $large_data,
+				),
+				function( $enabled_sizes ) {
+					$enabled_sizes['medium_large'] = false;
+					$enabled_sizes['large']        = false;
+					return $enabled_sizes;
+				},
+				array( 'thumbnail', 'medium' ),
+			),
+			array(
+				array(
+					'thumbnail' => $thumbnail_data,
+					'medium'    => $medium_data,
+					'custom'    => $custom_data,
+				),
+				function( $enabled_sizes ) {
+					unset( $enabled_sizes['medium'] );
+					$enabled_sizes['custom'] = true;
+					return $enabled_sizes;
+				},
+				array( 'thumbnail', 'custom' ),
+			),
+		);
+	}
+
+	/**
+	 * Test the `_wp_maybe_scale_and_rotate_image()` function.
+	 *
+	 * @dataProvider data_test__wp_maybe_scale_and_rotate_image
+	 *
+	 * @ticket 55443
+	 */
+	public function test__wp_maybe_scale_and_rotate_image( $file, $imagesize, $mime_type, $expected ) {
+		if ( ! wp_image_editor_supports( array( 'mime_type' => $mime_type ) ) ) {
+			$this->markTestSkipped( sprintf( 'This test requires %s support.', $mime_type ) );
+		}
+
+		$attributes    = array( 'post_mime_type' => $mime_type );
+		$attachment_id = $this->factory->attachment->create_object( $file, 0, $attributes );
+		$exif_meta     = wp_read_image_metadata( $file );
+
+		list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type );
+
+		$this->assertSame( $expected['rotated'], $rotated );
+		$this->assertSame( $expected['resized'], $resized );
+		$this->assertSame( $expected['size'], $editor->get_size() );
+	}
+
+	/**
+	 * Data provider for the `test__wp_maybe_scale_and_rotate_image()` test.
+	 *
+	 * @return array
+	 */
+	public function data_test__wp_maybe_scale_and_rotate_image() {
+		return array(
+
+			// Image that will be scaled.
+			array(
+				DIR_TESTDATA . '/images/test-image-large.jpg',
+				array( 3000, 2250 ),
+				'image/jpeg',
+				array(
+					'rotated' => false,
+					'resized' => true,
+					'size'    => array(
+						'width'  => 2560,
+						'height' => 1920,
+					),
+				),
+			),
+
+			// Image that will not be scaled.
+			array(
+				DIR_TESTDATA . '/images/canola.jpg',
+				array( 640, 480 ),
+				'image/jpeg',
+				array(
+					'rotated' => false,
+					'resized' => false,
+					'size'    => array(
+						'width'  => 640,
+						'height' => 480,
+					),
+				),
+			),
+
+			// Image that will be flipped.
+			array(
+				DIR_TESTDATA . '/images/test-image-upside-down.jpg',
+				array( 600, 450 ),
+				'image/jpeg',
+				array(
+					'rotated' => true,
+					'resized' => false,
+					'size'    => array(
+						'width'  => 600,
+						'height' => 450,
+					),
+				),
+			),
+
+			// Image that will be rotated.
+			array(
+				DIR_TESTDATA . '/images/test-image-rotated-90ccw.jpg',
+				array( 1200, 1800 ),
+				'image/jpeg',
+				array(
+					'rotated' => true,
+					'resized' => false,
+					'size'    => array(
+						'width'  => 1800,
+						'height' => 1200,
+					),
+				),
+			),
+
+			// Image that will not be rotated - WebP Exif is not supported in PHP.
+			array(
+				DIR_TESTDATA . '/images/test-image-rotated-90cw.webp',
+				array( 1024, 768 ),
+				'image/webp',
+				array(
+					'rotated' => false,
+					'resized' => false,
+					'size'    => array(
+						'width'  => 1024,
+						'height' => 768,
+					),
+				),
+			),
+
+		);
+	}
+
+	/**
+	 * Test the `_wp_get_image_suffix()` function.
+	 * @dataProvider data_test__wp_get_image_suffix
+	 *
+	 * @ticket 55443
+	 */
+	public function test__wp_get_image_suffix( $resized, $rotated, $expected ) {
+		$this->assertSame( $expected, _wp_get_image_suffix( $resized, $rotated ) );
+	}
+
+	/**
+	 * Data provider for the `test__wp_get_image_suffix()` test.
+	 */
+	public function data_test__wp_get_image_suffix() {
+		return array(
+			array( false, false, '' ),
+			array( true, false, 'scaled' ),
+			array( false, true, 'rotated' ),
+			array( true, true, 'scaled' ),
+		);
+	}
 }
diff --git tests/phpunit/tests/media.php tests/phpunit/tests/media.php
index 0341170721..592b19e328 100644
--- tests/phpunit/tests/media.php
+++ tests/phpunit/tests/media.php
@@ -2251,11 +2251,14 @@ EOF;
 		// Do not add width, height, and loading.
 		add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
 		add_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
+		add_filter( 'wp_content_image_mimes', '__return_empty_array' );
 
 		$this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
 
 		remove_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
 		remove_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
+		remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
+
 	}
 
 	/**
@@ -2289,9 +2292,12 @@ EOF;
 		$img = wp_img_tag_add_loading_attr( $img, 'test' );
 		$img = wp_img_tag_add_decoding_attr( $img, 'the_content' );
 		$img = preg_replace( '|<img ([^>]+) />|', '<img $1 ' . 'srcset="image2x.jpg 2x" />', $img );
+		add_filter( 'wp_content_image_mimes', '__return_empty_array' );
 
 		// The content filter should return the image unchanged.
 		$this->assertSame( $img, wp_filter_content_tags( $img ) );
+
+		remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
 	}
 
 	/**
@@ -2361,6 +2367,7 @@ EOF;
 		add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
 		add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
 		add_filter( 'wp_img_tag_add_decoding_attr', '__return_false' );
+		add_filter( 'wp_content_image_mimes', '__return_empty_array' );
 
 		add_filter(
 			'wp_content_img_tag',
@@ -2423,6 +2430,7 @@ EOF;
 	 * @requires function imagejpeg
 	 */
 	public function test_wp_filter_content_tags_schemes() {
+		add_filter( 'wp_content_image_mimes', '__return_empty_array' );
 		$image_meta = wp_get_attachment_metadata( self::$large_id );
 		$size_array = $this->get_image_size_array_from_meta( $image_meta, 'medium' );
 
@@ -2468,6 +2476,7 @@ EOF;
 		$actual = wp_filter_content_tags( $unfiltered );
 
 		$this->assertSame( $expected, $actual );
+		remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
 	}
 
 	/**
@@ -2961,11 +2970,13 @@ EOF;
 		// Do not add loading, srcset, and sizes.
 		add_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
 		add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
+		add_filter( 'wp_content_image_mimes', '__return_empty_array' );
 
 		$this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
 
 		remove_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
 		remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
+		remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
 	}
 
 	/**
@@ -3041,11 +3052,13 @@ EOF;
 		// Do not add width, height, srcset, and sizes.
 		add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
 		add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
+		add_filter( 'wp_content_image_mimes', '__return_empty_array' );
 
 		$this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
 
 		remove_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
 		remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
+		remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
 	}
 
 	/**
@@ -3074,9 +3087,13 @@ EOF;
 		// Enable globally for all tags.
 		add_filter( 'wp_lazy_loading_enabled', '__return_true' );
 
+		add_filter( 'wp_content_image_mimes', '__return_empty_array' );
+
 		$this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
 		remove_filter( 'wp_lazy_loading_enabled', '__return_true' );
 		remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
+		remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
+
 	}
 
 	/**
@@ -3101,9 +3118,12 @@ EOF;
 		// Disable globally for all tags.
 		add_filter( 'wp_lazy_loading_enabled', '__return_false' );
 
+		add_filter( 'wp_content_image_mimes', '__return_empty_array' );
+
 		$this->assertSame( $content, wp_filter_content_tags( $content ) );
 		remove_filter( 'wp_lazy_loading_enabled', '__return_false' );
 		remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
+		remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
 	}
 
 	/**
@@ -3529,6 +3549,7 @@ EOF;
 	 */
 	function test_wp_filter_content_tags_with_wp_get_loading_attr_default() {
 		global $wp_query, $wp_the_query;
+		add_filter( 'wp_content_image_mimes', '__return_empty_array' );
 
 		$img1         = get_image_tag( self::$large_id, '', '', '', 'large' );
 		$iframe1      = '<iframe src="https://www.example.com" width="640" height="360"></iframe>';
@@ -3564,6 +3585,7 @@ EOF;
 			$content_filtered = wp_filter_content_tags( $content_unfiltered, 'the_content' );
 			remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
 		}
+		remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
 
 		// After filtering, the first image should not be lazy-loaded while the other ones should be.
 		$this->assertSame( $content_expected, $content_filtered );
@@ -3613,6 +3635,169 @@ EOF;
 		// Clean up the above filter.
 		remove_filter( 'wp_omit_loading_attr_threshold', '__return_null', 100 );
 	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test_wp_image_use_alternate_mime_types_replaces_jpg_with_webp_where_available() {
+		if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+			$this->markTestSkipped( 'This test requires WebP support.' );
+		}
+
+		// The attachment $large_id is a JPEG image, so it gets WebP files generated by default.
+		$tag          = wp_get_attachment_image( self::$large_id, 'full' );
+		$expected_tag = $tag;
+
+		$metadata = wp_get_attachment_metadata( self::$large_id );
+		foreach ( $metadata['sizes'] as $size => $properties ) {
+			// Some sizes may not have WebP if the WebP file is larger than the JPEG for the size.
+			if ( ! isset( $properties['sources']['image/webp'] ) ) {
+				continue;
+			}
+			$expected_tag = str_replace( $properties['sources']['image/jpeg']['file'], $properties['sources']['image/webp']['file'], $expected_tag );
+		}
+		// Same applies to the full size.
+		if ( isset( $metadata['sources']['image/webp'] ) ) {
+			$expected_tag = str_replace( $metadata['sources']['image/jpeg']['file'], $metadata['sources']['image/webp']['file'], $expected_tag );
+		}
+
+		$this->assertNotSame( $tag, $expected_tag );
+		$this->assertSame( $expected_tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test_wp_image_use_alternate_mime_types_does_not_replace_jpg_when_webp_is_not_available() {
+		if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+			$this->markTestSkipped( 'This test requires WebP support.' );
+		}
+
+		// The attachment $large_id is a JPEG image, so it gets WebP files generated by default.
+		$tag = wp_get_attachment_image( self::$large_id, 'full' );
+
+		// Update attachment metadata as if the image had no WebP available for any sub-sizes and the full size.
+		$metadata = wp_get_attachment_metadata( self::$large_id );
+		foreach ( $metadata['sizes'] as $size => $properties ) {
+			unset( $metadata['sizes'][ $size ]['sources']['image/webp'] );
+		}
+		unset( $metadata['sources']['image/webp'] );
+		wp_update_attachment_metadata( self::$large_id, $metadata );
+
+		$this->assertSame( $tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test_wp_image_use_alternate_mime_types_still_replaces_jpg_subsizes_when_webp_is_not_available_for_full_size() {
+		if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+			$this->markTestSkipped( 'This test requires WebP support.' );
+		}
+
+		// The attachment $large_id is a JPEG image, so it gets WebP files generated by default.
+		$tag          = wp_get_attachment_image( self::$large_id, 'full' );
+		$expected_tag = $tag;
+
+		// Update attachment metadata as if the image had no WebP available for the full size.
+		$metadata = wp_get_attachment_metadata( self::$large_id );
+		unset( $metadata['sources']['image/webp'] );
+		wp_update_attachment_metadata( self::$large_id, $metadata );
+
+		foreach ( $metadata['sizes'] as $size => $properties ) {
+			// Some sizes may not have WebP if the WebP file is larger than the JPEG for the size.
+			if ( ! isset( $properties['sources']['image/webp'] ) ) {
+				continue;
+			}
+			$expected_tag = str_replace( $properties['sources']['image/jpeg']['file'], $properties['sources']['image/webp']['file'], $expected_tag );
+		}
+
+		$this->assertNotSame( $tag, $expected_tag );
+		$this->assertSame( $expected_tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test_wp_image_use_alternate_mime_types_respects_wp_content_image_mimes_filter() {
+		if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+			$this->markTestSkipped( 'This test requires WebP support.' );
+		}
+
+		// The attachment $large_id is a JPEG image, so it gets WebP files generated by default.
+		$tag = wp_get_attachment_image( self::$large_id, 'full' );
+
+		// Invalid filter value results in no changes to content.
+		add_filter( 'wp_content_image_mimes', '__return_false' );
+		$this->assertSame( $tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
+
+		// Empty array results in no changes to content.
+		add_filter( 'wp_content_image_mimes', '__return_empty_array' );
+		$this->assertSame( $tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
+
+		// Preferring JPEG over WebP results in no changes to content.
+		add_filter(
+			'wp_content_image_mimes',
+			function() {
+				return array( 'image/jpeg', 'image/webp' );
+			}
+		);
+		$this->assertSame( $tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test__wp_in_front_end_context_without_wp_query() {
+		unset( $GLOBALS['wp_query'] );
+
+		$this->assertFalse( _wp_in_front_end_context() );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test__wp_in_front_end_context_with_feed() {
+		remove_all_actions( 'template_redirect' );
+		do_action( 'template_redirect' );
+		$GLOBALS['wp_query']->is_feed = true;
+
+		$this->assertFalse( _wp_in_front_end_context() );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test__wp_in_front_end_context_before_and_after_template_redirect() {
+		$result = _wp_in_front_end_context();
+
+		remove_all_actions( 'template_redirect' );
+		do_action( 'template_redirect' );
+
+		$this->assertFalse( $result );
+		$this->assertTrue( _wp_in_front_end_context() );
+	}
+
+	/**
+	 * @ticket 55443
+	 */
+	public function test__wp_in_front_end_context_within_wp_head() {
+		remove_all_actions( 'template_redirect' );
+		do_action( 'template_redirect' );
+
+		// Call function within a 'wp_head' callback.
+		remove_all_actions( 'wp_head' );
+		$result = null;
+		add_action(
+			'wp_head',
+			function() use ( &$result ) {
+				$result = _wp_in_front_end_context();
+			}
+		);
+		do_action( 'wp_head' );
+
+		$this->assertFalse( $result );
+	}
 }
 
 /**
