WordPress 附件元数据生成中的缩略图生成机制剖析
大家好,今天我们来深入探讨 WordPress 的 wp_generate_attachment_metadata
函数,重点剖析其缩略图生成机制。理解这一机制,对于定制 WordPress 媒体处理流程、优化图片性能以及开发相关插件都至关重要。
1. wp_generate_attachment_metadata
函数概览
wp_generate_attachment_metadata
函数位于 wp-includes/media.php
文件中,其主要作用是为上传的附件生成元数据,包括:
- 文件基本信息:文件名、文件大小、MIME 类型等。
- 缩略图信息:不同尺寸的缩略图路径、尺寸等。
- 图片 Exif 信息:相机型号、拍摄时间、光圈等(如果存在)。
该函数接受附件 ID 作为参数,并返回一个包含元数据的数组。如果生成失败,则返回 false
。
函数的基本结构如下:
function wp_generate_attachment_metadata( $attachment_id, $file = '' ) {
// 1. 获取附件文件路径
$file = get_attached_file( $attachment_id );
// 2. 检查文件是否存在
if ( ! file_exists( $file ) ) {
return false;
}
// 3. 获取附件 MIME 类型
$mime_type = get_post_mime_type( $attachment_id );
// 4. 根据 MIME 类型选择处理函数
if ( strpos( $mime_type, 'image/' ) === 0 ) {
// 处理图片
$metadata = wp_generate_attachment_metadata_image( $attachment_id, $file );
} elseif ( strpos( $mime_type, 'video/' ) === 0 ) {
// 处理视频 (此处省略视频处理部分)
$metadata = wp_generate_attachment_metadata_video( $attachment_id, $file );
} else {
// 其他类型文件
$metadata = apply_filters( 'wp_generate_attachment_metadata', array(), $attachment_id, $file );
}
// 5. 更新附件元数据
if ( $metadata ) {
wp_update_attachment_metadata( $attachment_id, $metadata );
}
// 6. 返回元数据
return $metadata;
}
可以看到,wp_generate_attachment_metadata
函数本身并不直接处理缩略图生成,而是根据附件的 MIME 类型,调用相应的处理函数。对于图片,它调用了 wp_generate_attachment_metadata_image
函数。
2. wp_generate_attachment_metadata_image
函数:缩略图生成的关键
wp_generate_attachment_metadata_image
函数位于 wp-admin/includes/image.php
文件中,负责处理图片类型的附件,包括:
- 读取图片尺寸
- 生成缩略图
- 读取 Exif 信息(如果存在)
函数的基本结构如下:
function wp_generate_attachment_metadata_image( $attachment_id, $file ) {
$metadata = array();
// 1. 获取图片尺寸
$imagesize = getimagesize( $file );
if ( ! $imagesize ) {
return false;
}
list( $width, $height, $type, $attr ) = $imagesize;
$metadata['width'] = $width;
$metadata['height'] = $height;
// 2. 获取文件信息
$stat = stat( $file );
$metadata['file'] = wp_basename( $file );
$metadata['filesize'] = $stat['size'];
// 3. 生成不同尺寸的缩略图
$sizes = get_intermediate_image_sizes();
foreach ( $sizes as $size ) {
$resized = image_make_intermediate_size( $file, $metadata['width'], $metadata['height'], $size );
if ( $resized ) {
$metadata['sizes'][ $size ] = $resized;
}
}
// 4. 根据配置生成完整尺寸的副本
if ( isset( $GLOBALS['wp_version'] ) && version_compare( $GLOBALS['wp_version'], '5.3', '>=' ) ) {
$big_image_size_threshold = apply_filters( 'big_image_size_threshold', 2560 ); // WordPress 5.3+ 引入
if ( $metadata['width'] > $big_image_size_threshold || $metadata['height'] > $big_image_size_threshold ) {
$scaled = wp_get_scale_value( $metadata['width'], $metadata['height'], $big_image_size_threshold );
if ( $scaled < 1 ) {
$scaled_file = image_resize( $file, round( $metadata['width'] * $scaled ), round( $metadata['height'] * $scaled ), true );
if ( $scaled_file ) {
$scaled_file_path = _wp_relative_upload_path( $scaled_file );
$metadata['sizes']['scaled'] = array(
'file' => wp_basename( $scaled_file ),
'width' => round( $metadata['width'] * $scaled ),
'height' => round( $metadata['height'] * $scaled ),
'mime-type' => $imagesize['mime'],
);
// Clean up original metadata, as 'sizes' is used for scaled images in wp_get_attachment_image_src().
unset( $metadata['sizes']['full'] );
}
}
}
}
// 5. 读取 Exif 信息
$image_meta = wp_read_image_metadata( $file );
if ( $image_meta ) {
$metadata['image_meta'] = $image_meta;
}
// 6. 应用过滤器,允许修改元数据
return apply_filters( 'wp_generate_attachment_metadata', $metadata, $attachment_id, $file );
}
可以看到,缩略图生成的关键在于第 3 步的循环,它遍历了 get_intermediate_image_sizes()
函数返回的尺寸列表,并调用 image_make_intermediate_size
函数生成对应尺寸的缩略图。
3. get_intermediate_image_sizes
函数:定义缩略图尺寸
get_intermediate_image_sizes
函数位于 wp-includes/media.php
文件中,用于获取已注册的缩略图尺寸列表。这些尺寸通常在主题的 functions.php
文件中或者通过插件使用 add_image_size
函数定义。
function get_intermediate_image_sizes( $exclude = array() ) {
global $_wp_additional_image_sizes;
$builtin_sizes = array( 'thumbnail', 'medium', 'medium_large', 'large' );
$intermediate_sizes = array_unique( array_merge( $builtin_sizes, array_keys( (array) $_wp_additional_image_sizes ) ) );
if ( ! empty( $exclude ) ) {
$intermediate_sizes = array_diff( $intermediate_sizes, (array) $exclude );
}
return $intermediate_sizes;
}
该函数返回一个数组,包含 WordPress 默认的缩略图尺寸(thumbnail
、medium
、medium_large
、large
)以及通过 add_image_size
函数自定义的尺寸。
add_image_size
函数:注册自定义缩略图尺寸
add_image_size
函数用于注册自定义的缩略图尺寸。它接受四个参数:
$name
:缩略图尺寸的名称(例如:my-custom-size
)。$width
:缩略图的宽度(像素)。$height
:缩略图的高度(像素)。$crop
:是否裁剪图片。如果为true
,则裁剪图片以适应指定的尺寸;如果为false
,则缩放图片,保持宽高比。
add_image_size( 'my-custom-size', 200, 200, true ); // 创建一个 200x200 的裁剪缩略图
4. image_make_intermediate_size
函数:生成缩略图
image_make_intermediate_size
函数位于 wp-admin/includes/image.php
文件中,负责根据指定的尺寸和裁剪选项生成缩略图。
function image_make_intermediate_size( $file, $width, $height, $size ) {
global $_wp_additional_image_sizes;
if ( is_array( $size ) ) {
$crop = isset( $size['crop'] ) ? $size['crop'] : false;
$width = isset( $size['width'] ) ? $size['width'] : 0;
$height = isset( $size['height'] ) ? $size['height'] : 0;
} else {
if ( isset( $_wp_additional_image_sizes[ $size ] ) ) {
$width = absint( $_wp_additional_image_sizes[ $size ]['width'] );
$height = absint( $_wp_additional_image_sizes[ $size ]['height'] );
$crop = $_wp_additional_image_sizes[ $size ]['crop'];
} else {
return false;
}
}
if ( $width || $height ) {
$resized_file = image_resize( $file, $width, $height, $crop );
if ( ! is_wp_error( $resized_file ) && $resized_file ) {
$info = getimagesize( $resized_file );
$resized = array(
'file' => wp_basename( $resized_file ),
'width' => $info[0],
'height' => $info[1],
'mime-type' => $info['mime'],
);
return $resized;
}
}
return false;
}
该函数首先根据传入的 $size
参数,获取对应的宽度、高度和裁剪选项。然后,调用 image_resize
函数生成缩略图。如果生成成功,则返回一个包含缩略图信息的数组;如果生成失败,则返回 false
。
5. image_resize
函数:底层图片处理
image_resize
函数位于 wp-includes/media.php
文件中,是实际进行图片缩放和裁剪操作的函数。它使用 WordPress 的 Image Editor API 来处理图片。
function image_resize( $file, $max_w, $max_h, $crop = false, $suffix = null, $dest_path = null, $jpeg_quality = 90 ) {
$image = wp_load_image( $file );
if ( is_wp_error( $image ) ) {
return $image;
}
$size = @getimagesize( $file );
if ( ! $size ) {
return new WP_Error( 'invalid_image', __('Could not read image size'), $file );
}
list($orig_w, $orig_h, $orig_type) = $size;
$dims = image_resize_dimensions( $orig_w, $orig_h, $max_w, $max_h, $crop );
if ( ! $dims ) {
return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') );
}
list($dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) = $dims;
$newimage = wp_imagecreatetruecolor( $dst_w, $dst_h );
imagealphablending( $newimage, false );
imagesavealpha( $newimage, true );
$result = imagecopyresampled( $newimage, $image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h );
if ( ! $result ) {
imagedestroy( $newimage );
return new WP_Error( 'image_resize_failed', __('Image resize failed.') );
}
if ( is_resource( $image ) ) {
imagedestroy( $image );
}
if ( ! $suffix ) {
$suffix = uniqid();
}
$info = pathinfo( $file );
$dir = $info['dirname'];
$ext = $info['extension'];
$name = wp_basename( $file, ".$ext" );
$new_file = "{$dir}/{$name}-{$suffix}.{$ext}";
if ( ! empty( $dest_path ) && @is_dir( $dest_path ) ) {
$new_file = trailingslashit( $dest_path ) . wp_basename( $new_file );
}
// Convert output to imagejpeg if qaulity is defined.
if ( IMAGETYPE_JPEG == $orig_type ) {
if ( imagetypes() & IMG_JPG ) {
imagejpeg( $newimage, $new_file, apply_filters( 'jpeg_quality', $jpeg_quality, 'image_resize' ) );
}
} else {
switch ( $orig_type ) {
case IMAGETYPE_GIF:
if ( imagetypes() & IMG_GIF ) {
imagegif( $newimage, $new_file );
}
break;
case IMAGETYPE_PNG:
if ( imagetypes() & IMG_PNG ) {
imagepng( $newimage, $new_file, apply_filters( 'wp_image_quality', 9, 'image_resize' ) );
}
break;
default:
/**
* Fires when creating a resized image.
*
* @since 3.5.0
*
* @param resource $newimage The resized image.
* @param string $new_file New image name.
* @param int $orig_type Image type.
* @param string $file The original image path.
*/
do_action( 'wp_handle_resized_image', $newimage, $new_file, $orig_type, $file );
break;
}
}
imagedestroy( $newimage );
/**
* Fires after a resized image is created.
*
* @since 2.1.0
*
* @param string $new_file New image path.
* @param int $orig_w Original width.
* @param int $orig_h Original height.
* @param int $max_w Maximum width.
* @param int $max_h Maximum height.
* @param bool $crop Whether to crop.
*/
do_action( 'image_resize', $new_file, $orig_w, $orig_h, $max_w, $max_h, $crop );
return $new_file;
}
该函数的主要步骤包括:
- 加载图片:使用
wp_load_image
函数加载图片。 - 计算尺寸:使用
image_resize_dimensions
函数计算缩放和裁剪后的尺寸。 - 创建画布:使用
wp_imagecreatetruecolor
函数创建一个新的画布。 - 重采样:使用
imagecopyresampled
函数将原始图片复制到新的画布上,并进行缩放和裁剪。 - 保存图片:根据原始图片的类型,使用相应的函数(
imagejpeg
、imagegif
、imagepng
)将新的图片保存到文件中。
image_resize_dimensions
函数:尺寸计算
image_resize_dimensions
函数位于 wp-includes/media.php
文件中,用于计算缩放和裁剪后的尺寸。它是 image_resize
函数的关键组成部分,负责确定如何将原始图片调整到目标尺寸。
这个函数接收原始图片的宽度和高度,目标宽度和高度,以及一个裁剪标志作为输入。它的输出是一个包含缩放和裁剪参数的数组,这些参数用于 imagecopyresampled
函数。
function image_resize_dimensions( $orig_w, $orig_h, $dest_w, $dest_h, $crop = false ) {
if ( $orig_w <= 0 || $orig_h <= 0 ) {
return false;
}
// At least one of $dest_w or $dest_h must be specific.
if ( $dest_w <= 0 && $dest_h <= 0 ) {
return false;
}
/**
* Filters whether to skip the default image resize dimensions calculation.
*
* Passing a non-null value to the filter will effectively short-circuit
* the default calculation.
*
* @since 4.4.0
*
* @param null|mixed $value Whether to short-circuit the default calculation. Default null.
* @param int $orig_w Original width.
* @param int $orig_h Original height.
* @param int $dest_w New width.
* @param int $dest_h New height.
* @param bool $crop Whether to crop.
*/
$output = apply_filters( 'image_resize_dimensions', null, $orig_w, $orig_h, $dest_w, $dest_h, $crop );
if ( null !== $output ) {
return $output;
}
if ( $crop ) {
// crop the largest possible portion of the original image that we can size to $dest_w x $dest_h
$aspect_ratio = $orig_w / $orig_h;
$new_w = min($dest_w, $orig_w);
$new_h = min($dest_h, $orig_h);
if ( ! $new_w ) {
$new_w = intval( $new_h * $aspect_ratio );
}
if ( ! $new_h ) {
$new_h = intval( $new_w / $aspect_ratio );
}
$dest_ratio = $dest_w / $dest_h;
if ( $aspect_ratio >= $dest_ratio ) {
$src_h = $orig_h;
$src_w = round($src_h * $dest_ratio);
$src_x = round(($orig_w - $src_w) / 2);
$src_y = 0;
} else {
$src_w = $orig_w;
$src_h = round($src_w / $dest_ratio);
$src_x = 0;
$src_y = round(($orig_h - $src_h) / 2);
}
$dst_x = 0;
$dst_y = 0;
$dst_w = $dest_w;
$dst_h = $dest_h;
} else {
// don't crop, just resize using $dest_w x $dest_h as a maximum bounding box
$max_w = intval($dest_w);
$max_h = intval($dest_h);
$aspect_ratio = $orig_w / $orig_h;
if ( $max_h == 0 ) {
$max_h = intval($max_w / $aspect_ratio);
}
if ( $max_w == 0 ) {
$max_w = intval($max_h * $aspect_ratio);
}
$w = $orig_w;
$h = $orig_h;
if ( $w > $max_w || $h > $max_h ) {
$percent = $w / $max_w;
$h = floor($h / $percent);
$w = floor($w / $percent);
}
if ( $h > $max_h ) {
$percent = $h / $max_h;
$w = floor($w / $percent);
$h = floor($h / $percent);
}
$dst_x = 0;
$dst_y = 0;
$dst_w = $w;
$dst_h = $h;
$src_x = 0;
$src_y = 0;
$src_w = $orig_w;
$src_h = $orig_h;
}
// Return an array of coord triplets that define the image regions to use
return array( 0, 0, (int) $src_x, (int) $src_y, (int) $dst_w, (int) $dst_h, (int) $src_w, (int) $src_h );
}
该函数首先判断是否需要裁剪。如果需要裁剪,则计算裁剪区域的坐标和尺寸,以保证裁剪后的图片能够完全覆盖目标尺寸。如果不需要裁剪,则计算缩放后的尺寸,以保证缩放后的图片能够完全包含在目标尺寸内,并且保持原始图片的宽高比。
6. 缓存机制
WordPress 并没有内置针对 wp_generate_attachment_metadata
函数的显式缓存机制。 然而,由于缩略图一旦生成,就会存储在文件系统中,后续请求可以直接从文件系统中读取,从而避免重复生成。
7. 过滤器
wp_generate_attachment_metadata
函数和 wp_generate_attachment_metadata_image
函数都使用了过滤器,允许开发者修改元数据生成过程。
wp_generate_attachment_metadata
:可以修改最终的元数据数组。jpeg_quality
:可以修改 JPEG 图片的质量。wp_image_editors
: 允许修改 WordPress 使用的图像编辑器类。image_resize_dimensions
: 允许修改image_resize_dimensions
函数计算出的尺寸。
例如,可以使用 wp_generate_attachment_metadata
过滤器添加自定义的元数据:
add_filter( 'wp_generate_attachment_metadata', 'my_custom_attachment_metadata', 10, 3 );
function my_custom_attachment_metadata( $metadata, $attachment_id, $file ) {
$metadata['my_custom_data'] = 'My custom value';
return $metadata;
}
8. 总结:关键步骤与自定义空间
wp_generate_attachment_metadata
函数的缩略图生成机制可以概括为以下几个关键步骤:
- 获取已注册的缩略图尺寸列表(
get_intermediate_image_sizes
)。 - 遍历尺寸列表,并为每个尺寸调用
image_make_intermediate_size
函数。 image_make_intermediate_size
函数调用image_resize
函数进行实际的图片缩放和裁剪操作。image_resize
函数使用image_resize_dimensions
函数计算缩放和裁剪后的尺寸,并使用 WordPress 的 Image Editor API 处理图片。- 通过各种过滤器,开发者可以自定义缩略图生成过程,例如添加自定义尺寸、修改 JPEG 质量、或者使用自定义的图片处理逻辑。
理解这些步骤,有助于开发者更好地控制 WordPress 的媒体处理流程,优化图片性能,并开发相关插件。
总的来说,WordPress 的缩略图生成机制经过精心设计,兼顾了灵活性和可扩展性。 开发者可以根据自己的需求,定制缩略图的尺寸、裁剪方式和处理方式,从而满足不同的应用场景。通过本文的分析,相信大家对这一机制有了更深入的了解。