分析 wp_generate_attachment_metadata 的缩略图生成机制

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 默认的缩略图尺寸(thumbnailmediummedium_largelarge)以及通过 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;
}

该函数的主要步骤包括:

  1. 加载图片:使用 wp_load_image 函数加载图片。
  2. 计算尺寸:使用 image_resize_dimensions 函数计算缩放和裁剪后的尺寸。
  3. 创建画布:使用 wp_imagecreatetruecolor 函数创建一个新的画布。
  4. 重采样:使用 imagecopyresampled 函数将原始图片复制到新的画布上,并进行缩放和裁剪。
  5. 保存图片:根据原始图片的类型,使用相应的函数(imagejpegimagegifimagepng)将新的图片保存到文件中。

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 函数的缩略图生成机制可以概括为以下几个关键步骤:

  1. 获取已注册的缩略图尺寸列表(get_intermediate_image_sizes)。
  2. 遍历尺寸列表,并为每个尺寸调用 image_make_intermediate_size 函数。
  3. image_make_intermediate_size 函数调用 image_resize 函数进行实际的图片缩放和裁剪操作。
  4. image_resize 函数使用 image_resize_dimensions 函数计算缩放和裁剪后的尺寸,并使用 WordPress 的 Image Editor API 处理图片。
  5. 通过各种过滤器,开发者可以自定义缩略图生成过程,例如添加自定义尺寸、修改 JPEG 质量、或者使用自定义的图片处理逻辑。

理解这些步骤,有助于开发者更好地控制 WordPress 的媒体处理流程,优化图片性能,并开发相关插件。

总的来说,WordPress 的缩略图生成机制经过精心设计,兼顾了灵活性和可扩展性。 开发者可以根据自己的需求,定制缩略图的尺寸、裁剪方式和处理方式,从而满足不同的应用场景。通过本文的分析,相信大家对这一机制有了更深入的了解。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注