文章目录[隐藏]
开发指南:打造网站内嵌的在线图片基础处理与美化编辑器
摘要
在当今数字化时代,视觉内容已成为网站吸引用户的关键因素。本文将详细介绍如何通过WordPress程序的代码二次开发,实现一个内嵌于网站的在线图片基础处理与美化编辑器。我们将从需求分析、技术选型、架构设计到具体实现步骤,全面解析如何将常用互联网小工具功能集成到WordPress平台中,帮助网站管理员和内容创作者在不离开网站的情况下完成图片编辑工作,提升工作效率和用户体验。
一、项目背景与需求分析
1.1 为什么需要内嵌图片编辑器?
随着内容创作需求的日益增长,网站管理员和内容创作者经常需要处理大量图片。传统的图片处理流程通常包括:下载图片→使用专业软件编辑→重新上传到网站。这个过程不仅耗时耗力,还可能导致图片质量损失。内嵌图片编辑器可以解决以下痛点:
- 提高工作效率:无需切换不同应用程序,直接在网站后台完成编辑
- 降低技术门槛:提供简单易用的界面,无需专业设计技能
- 保持一致性:确保所有图片符合网站风格和尺寸要求
- 减少存储负担:避免同一图片的多个版本占用服务器空间
1.2 功能需求分析
基于常见使用场景,我们需要实现以下核心功能:
- 基础编辑功能:裁剪、旋转、调整大小、翻转
- 色彩调整:亮度、对比度、饱和度、色相调整
- 滤镜效果:多种预设滤镜,支持自定义参数
- 添加元素:文字、形状、贴纸、水印
- 绘图工具:画笔、橡皮擦、形状绘制
- 图层管理:基本的图层操作功能
- 格式转换:支持常见图片格式转换
- 批量处理:对多张图片进行相同操作
1.3 技术可行性分析
WordPress作为全球最流行的内容管理系统,拥有强大的扩展性和丰富的API接口,为二次开发提供了良好基础。通过合理的技术选型和架构设计,完全可以实现一个功能完善的在线图片编辑器。
二、技术选型与架构设计
2.1 技术栈选择
前端技术
- React.js:组件化开发,提高代码复用性和维护性
- Fabric.js:强大的Canvas库,专门用于图像处理和操作
- Tailwind CSS:实用优先的CSS框架,快速构建美观界面
- Redux:状态管理,处理复杂编辑器状态
后端技术
- PHP:WordPress原生开发语言
- WordPress REST API:与前端通信,处理数据存储
- ImageMagick/GD库:服务器端图像处理
- MySQL:WordPress默认数据库
2.2 系统架构设计
用户界面层 (React + Fabric.js + Tailwind CSS)
↓
API通信层 (WordPress REST API + 自定义端点)
↓
业务逻辑层 (PHP插件,处理图像操作逻辑)
↓
数据处理层 (WordPress数据库 + 文件系统)
↓
图像处理层 (ImageMagick/GD库 + 缓存机制)
2.3 模块划分
- 编辑器核心模块:基于Fabric.js的图像操作核心
- 工具面板模块:各种编辑工具的UI实现
- 文件管理模块:图片上传、保存、导出功能
- 滤镜效果模块:预设滤镜和自定义滤镜
- 文字处理模块:字体、样式、排版功能
- 图层管理模块:图层操作和混合模式
- 批量处理模块:多图片批量操作
- 设置与配置模块:编辑器个性化设置
三、开发环境搭建与准备工作
3.1 开发环境配置
- 本地WordPress环境:使用Local by Flywheel或XAMPP搭建
- 代码编辑器:VS Code或PHPStorm
- 版本控制:Git + GitHub/GitLab
- 调试工具:浏览器开发者工具 + WordPress调试模式
3.2 WordPress插件基础结构
创建插件目录结构:
wp-image-editor/
├── wp-image-editor.php # 主插件文件
├── includes/
│ ├── class-editor-core.php # 编辑器核心类
│ ├── class-image-processor.php # 图像处理类
│ ├── class-file-manager.php # 文件管理类
│ └── class-api-endpoints.php # REST API端点
├── admin/
│ ├── css/ # 后台样式
│ ├── js/ # 后台脚本
│ └── views/ # 后台视图
├── public/
│ ├── css/ # 前台样式
│ ├── js/ # 前台脚本
│ └── views/ # 前台视图
├── assets/ # 静态资源
│ ├── fonts/ # 字体文件
│ ├── icons/ # 图标资源
│ └── presets/ # 预设配置
└── vendor/ # 第三方库
3.3 主插件文件配置
<?php
/**
* Plugin Name: WordPress内嵌图片编辑器
* Plugin URI: https://yourwebsite.com/
* Description: 在WordPress中内嵌在线图片基础处理与美化编辑器
* Version: 1.0.0
* Author: 您的名称
* License: GPL v2 or later
* Text Domain: wp-image-editor
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('WPIE_VERSION', '1.0.0');
define('WPIE_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WPIE_PLUGIN_URL', plugin_dir_url(__FILE__));
define('WPIE_UPLOAD_DIR', 'wpie-editor');
// 自动加载类文件
spl_autoload_register(function ($class_name) {
if (strpos($class_name, 'WPIE_') === 0) {
$file = WPIE_PLUGIN_DIR . 'includes/class-' . strtolower(str_replace('_', '-', $class_name)) . '.php';
if (file_exists($file)) {
require_once $file;
}
}
});
// 初始化插件
function wpie_init() {
// 检查WordPress版本
if (version_compare(get_bloginfo('version'), '5.0', '<')) {
add_action('admin_notices', function() {
echo '<div class="notice notice-error"><p>WordPress内嵌图片编辑器需要WordPress 5.0或更高版本。</p></div>';
});
return;
}
// 初始化核心类
$editor_core = new WPIE_Editor_Core();
$editor_core->init();
}
add_action('plugins_loaded', 'wpie_init');
// 激活/停用插件时的操作
register_activation_hook(__FILE__, 'wpie_activate');
register_deactivation_hook(__FILE__, 'wpie_deactivate');
function wpie_activate() {
// 创建必要的目录
$upload_dir = wp_upload_dir();
$wpie_dir = $upload_dir['basedir'] . '/' . WPIE_UPLOAD_DIR;
if (!file_exists($wpie_dir)) {
wp_mkdir_p($wpie_dir);
}
// 添加默认选项
add_option('wpie_settings', array(
'max_file_size' => 10, // MB
'allowed_formats' => array('jpg', 'jpeg', 'png', 'gif', 'webp'),
'default_quality' => 85,
'enable_watermark' => false,
'auto_save' => true
));
}
function wpie_deactivate() {
// 清理临时文件
$upload_dir = wp_upload_dir();
$wpie_dir = $upload_dir['basedir'] . '/' . WPIE_UPLOAD_DIR . '/temp';
if (file_exists($wpie_dir)) {
array_map('unlink', glob($wpie_dir . '/*'));
rmdir($wpie_dir);
}
}
四、核心编辑器实现
4.1 前端编辑器框架搭建
创建React应用作为编辑器前端:
// admin/js/editor-app.jsx
import React, { useState, useRef, useEffect } from 'react';
import ReactDOM from 'react-dom';
import { fabric } from 'fabric';
import ToolPanel from './components/ToolPanel';
import CanvasArea from './components/CanvasArea';
import LayerPanel from './components/LayerPanel';
import PropertyPanel from './components/PropertyPanel';
import Header from './components/Header';
import { EditorProvider } from './context/EditorContext';
import './styles/editor.css';
const App = () => {
const [canvas, setCanvas] = useState(null);
const [activeTool, setActiveTool] = useState('select');
const [history, setHistory] = useState([]);
const [historyIndex, setHistoryIndex] = useState(-1);
const canvasRef = useRef(null);
useEffect(() => {
// 初始化Fabric.js画布
const initCanvas = new fabric.Canvas('editor-canvas', {
width: 800,
height: 600,
backgroundColor: '#f5f5f5',
preserveObjectStacking: true,
});
setCanvas(initCanvas);
// 保存初始状态到历史记录
saveToHistory(initCanvas);
return () => {
initCanvas.dispose();
};
}, []);
const saveToHistory = (canvasInstance) => {
const json = canvasInstance.toJSON();
const newHistory = history.slice(0, historyIndex + 1);
newHistory.push(json);
setHistory(newHistory);
setHistoryIndex(newHistory.length - 1);
};
const handleUndo = () => {
if (historyIndex > 0) {
const newIndex = historyIndex - 1;
canvas.loadFromJSON(history[newIndex], () => {
canvas.renderAll();
});
setHistoryIndex(newIndex);
}
};
const handleRedo = () => {
if (historyIndex < history.length - 1) {
const newIndex = historyIndex + 1;
canvas.loadFromJSON(history[newIndex], () => {
canvas.renderAll();
});
setHistoryIndex(newIndex);
}
};
const handleCanvasChange = () => {
saveToHistory(canvas);
};
return (
<EditorProvider value={{ canvas, activeTool, setActiveTool }}>
<div className="editor-container">
<Header
onUndo={handleUndo}
onRedo={handleRedo}
canUndo={historyIndex > 0}
canRedo={historyIndex < history.length - 1}
/>
<div className="editor-main">
<ToolPanel />
<CanvasArea
canvasRef={canvasRef}
onCanvasChange={handleCanvasChange}
/>
<div className="editor-sidebar">
<PropertyPanel />
<LayerPanel />
</div>
</div>
</div>
</EditorProvider>
);
};
// 初始化编辑器
document.addEventListener('DOMContentLoaded', () => {
const editorContainer = document.getElementById('wpie-editor-container');
if (editorContainer) {
ReactDOM.render(<App />, editorContainer);
}
});
4.2 图像处理核心类实现
// includes/class-image-processor.php
class WPIE_Image_Processor {
private $image_path;
private $image_resource;
private $image_info;
public function __construct($image_path) {
$this->image_path = $image_path;
$this->image_info = getimagesize($image_path);
$this->load_image();
}
private function load_image() {
$mime_type = $this->image_info['mime'];
switch ($mime_type) {
case 'image/jpeg':
$this->image_resource = imagecreatefromjpeg($this->image_path);
break;
case 'image/png':
$this->image_resource = imagecreatefrompng($this->image_path);
break;
case 'image/gif':
$this->image_resource = imagecreatefromgif($this->image_path);
break;
case 'image/webp':
if (function_exists('imagecreatefromwebp')) {
$this->image_resource = imagecreatefromwebp($this->image_path);
}
break;
default:
throw new Exception('不支持的图片格式: ' . $mime_type);
}
if (!$this->image_resource) {
throw new Exception('无法加载图片文件');
}
}
public function resize($width, $height, $keep_aspect_ratio = true) {
$src_width = imagesx($this->image_resource);
$src_height = imagesy($this->image_resource);
if ($keep_aspect_ratio) {
$ratio = $src_width / $src_height;
if ($width / $height > $ratio) {
$width = $height * $ratio;
} else {
$height = $width / $ratio;
}
}
$new_image = imagecreatetruecolor($width, $height);
// 保持透明度
$this->preserve_transparency($new_image);
imagecopyresampled(
$new_image, $this->image_resource,
0, 0, 0, 0,
$width, $height,
$src_width, $src_height
);
imagedestroy($this->image_resource);
$this->image_resource = $new_image;
return $this;
}
public function crop($x, $y, $width, $height) {
$new_image = imagecreatetruecolor($width, $height);
// 保持透明度
$this->preserve_transparency($new_image);
imagecopy(
$new_image, $this->image_resource,
0, 0, $x, $y,
$width, $height
);
imagedestroy($this->image_resource);
$this->image_resource = $new_image;
return $this;
}
public function rotate($degrees) {
$transparent = imagecolorallocatealpha($this->image_resource, 0, 0, 0, 127);
$rotated = imagerotate($this->image_resource, $degrees, $transparent);
// 保持透明度
imagesavealpha($rotated, true);
imagealphablending($rotated, true);
imagedestroy($this->image_resource);
$this->image_resource = $rotated;
return $this;
}
public function adjust_brightness($level) {
imagefilter($this->image_resource, IMG_FILTER_BRIGHTNESS, $level);
return $this;
}
public function adjust_contrast($level) {
imagefilter($this->image_resource, IMG_FILTER_CONTRAST, $level);
return $this;
}
public function apply_filter($filter_type, $args = []) {
switch ($filter_type) {
case 'grayscale':
imagefilter($this->image_resource, IMG_FILTER_GRAYSCALE);
break;
case 'sepia':
imagefilter($this->image_resource, IMG_FILTER_GRAYSCALE);
imagefilter($this->image_resource, IMG_FILTER_COLORIZE, 100, 50, 0);
break;
case 'vintage':
imagefilter($this->image_resource, IMG_FILTER_BRIGHTNESS, -30);
imagefilter($this->image_resource, IMG_FILTER_CONTRAST, -10);
imagefilter($this->image_resource, IMG_FILTER_COLORIZE, 60, 30, 0);
break;
case 'blur':
$level = isset($args['level']) ? $args['level'] : 1;
for ($i = 0; $i < $level; $i++) {
imagefilter($this->image_resource, IMG_FILTER_GAUSSIAN_BLUR);
}
break;
}
return $this;
}
public function add_watermark($watermark_path, $position = 'bottom-right', $opacity = 50) {
$watermark = imagecreatefrompng($watermark_path);
$wm_width = imagesx($watermark);
$wm_height = imagesy($watermark);
$img_width = imagesx($this->image_resource);
$img_height = imagesy($this->image_resource);
// 计算水印位置
switch ($position) {
case 'top-left':
$dest_x = 10;
$dest_y = 10;
break;
case 'top-right':
$dest_x = $img_width - $wm_width - 10;
$dest_y = 10;
break;
case 'bottom-left':
$dest_x = 10;
$dest_y = $img_height - $wm_height - 10;
break;
case 'bottom-right':
$dest_x = $img_width - $wm_width - 10;
$dest_y = $img_height - $wm_height - 10;
break;
case 'center':
$dest_x = ($img_width - $wm_width) / 2;
$dest_y = ($img_height - $wm_height) / 2;
break;
default:
$dest_x = 10;
$dest_y = 10;
}
// 合并水印
$this->imagecopymerge_alpha(
$this->image_resource, $watermark,
$dest_x, $dest_y, 0, 0,
$wm_width, $wm_height,
$opacity
);
imagedestroy($watermark);
return $this;
}
$src_x, $src_y, $src_w, $src_h, $pct
) {
// 创建临时图像
$cut = imagecreatetruecolor($src_w, $src_h);
// 复制源图像
imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h);
imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h);
// 合并到目标图像
imagecopymerge($dst_im, $cut, $dst_x, $dst_y, 0, 0, $src_w, $src_h, $pct);
imagedestroy($cut);
}
private function preserve_transparency($new_image) {
$mime_type = $this->image_info['mime'];
if ($mime_type == 'image/png' || $mime_type == 'image/gif') {
imagealphablending($new_image, false);
imagesavealpha($new_image, true);
$transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127);
imagefilledrectangle($new_image, 0, 0, imagesx($new_image), imagesy($new_image), $transparent);
}
}
public function save($output_path, $quality = 85) {
$extension = strtolower(pathinfo($output_path, PATHINFO_EXTENSION));
switch ($extension) {
case 'jpg':
case 'jpeg':
imagejpeg($this->image_resource, $output_path, $quality);
break;
case 'png':
// PNG质量参数为0-9,需要转换
$png_quality = 9 - round(($quality / 100) * 9);
imagepng($this->image_resource, $output_path, $png_quality);
break;
case 'gif':
imagegif($this->image_resource, $output_path);
break;
case 'webp':
if (function_exists('imagewebp')) {
imagewebp($this->image_resource, $output_path, $quality);
}
break;
}
return $output_path;
}
public function get_image_data() {
ob_start();
$extension = strtolower(pathinfo($this->image_path, PATHINFO_EXTENSION));
switch ($extension) {
case 'jpg':
case 'jpeg':
imagejpeg($this->image_resource, null, 90);
break;
case 'png':
imagepng($this->image_resource, null, 9);
break;
case 'gif':
imagegif($this->image_resource);
break;
case 'webp':
if (function_exists('imagewebp')) {
imagewebp($this->image_resource, null, 90);
}
break;
}
$image_data = ob_get_contents();
ob_end_clean();
return 'data:' . $this->image_info['mime'] . ';base64,' . base64_encode($image_data);
}
public function __destruct() {
if ($this->image_resource) {
imagedestroy($this->image_resource);
}
}
}
4.3 工具面板组件实现
// admin/js/components/ToolPanel.jsx
import React, { useContext } from 'react';
import { EditorContext } from '../context/EditorContext';
import {
Crop, RotateCw, Type, Square, Circle,
PenTool, Eraser, Image as ImageIcon,
Filter, Layers, Settings
} from 'lucide-react';
const ToolPanel = () => {
const { activeTool, setActiveTool } = useContext(EditorContext);
const tools = [
{ id: 'select', name: '选择', icon: '↖' },
{ id: 'crop', name: '裁剪', icon: <Crop size={20} /> },
{ id: 'rotate', name: '旋转', icon: <RotateCw size={20} /> },
{ id: 'text', name: '文字', icon: <Type size={20} /> },
{ id: 'rectangle', name: '矩形', icon: <Square size={20} /> },
{ id: 'circle', name: '圆形', icon: <Circle size={20} /> },
{ id: 'brush', name: '画笔', icon: <PenTool size={20} /> },
{ id: 'eraser', name: '橡皮', icon: <Eraser size={20} /> },
{ id: 'image', name: '图片', icon: <ImageIcon size={20} /> },
{ id: 'filter', name: '滤镜', icon: <Filter size={20} /> },
{ id: 'layers', name: '图层', icon: <Layers size={20} /> },
{ id: 'settings', name: '设置', icon: <Settings size={20} /> }
];
const handleToolClick = (toolId) => {
setActiveTool(toolId);
// 根据工具类型执行相应操作
switch (toolId) {
case 'crop':
// 激活裁剪模式
break;
case 'text':
// 添加文字对象
break;
// ... 其他工具处理
}
};
return (
<div className="tool-panel">
<div className="tool-category">
<h3 className="tool-category-title">选择与变换</h3>
<div className="tool-grid">
{tools.slice(0, 4).map(tool => (
<button
key={tool.id}
className={`tool-button ${activeTool === tool.id ? 'active' : ''}`}
onClick={() => handleToolClick(tool.id)}
title={tool.name}
>
<span className="tool-icon">{tool.icon}</span>
<span className="tool-name">{tool.name}</span>
</button>
))}
</div>
</div>
<div className="tool-category">
<h3 className="tool-category-title">绘图工具</h3>
<div className="tool-grid">
{tools.slice(4, 8).map(tool => (
<button
key={tool.id}
className={`tool-button ${activeTool === tool.id ? 'active' : ''}`}
onClick={() => handleToolClick(tool.id)}
title={tool.name}
>
<span className="tool-icon">{tool.icon}</span>
<span className="tool-name">{tool.name}</span>
</button>
))}
</div>
</div>
<div className="tool-category">
<h3 className="tool-category-title">效果与设置</h3>
<div className="tool-grid">
{tools.slice(8).map(tool => (
<button
key={tool.id}
className={`tool-button ${activeTool === tool.id ? 'active' : ''}`}
onClick={() => handleToolClick(tool.id)}
title={tool.name}
>
<span className="tool-icon">{tool.icon}</span>
<span className="tool-name">{tool.name}</span>
</button>
))}
</div>
</div>
</div>
);
};
export default ToolPanel;
五、REST API接口设计与实现
5.1 自定义REST API端点
// includes/class-api-endpoints.php
class WPIE_API_Endpoints {
public function __construct() {
add_action('rest_api_init', array($this, 'register_routes'));
}
public function register_routes() {
// 上传图片
register_rest_route('wpie/v1', '/upload', array(
'methods' => 'POST',
'callback' => array($this, 'handle_upload'),
'permission_callback' => array($this, 'check_permission')
));
// 处理图片
register_rest_route('wpie/v1', '/process', array(
'methods' => 'POST',
'callback' => array($this, 'handle_process'),
'permission_callback' => array($this, 'check_permission')
));
// 应用滤镜
register_rest_route('wpie/v1', '/apply-filter', array(
'methods' => 'POST',
'callback' => array($this, 'apply_filter'),
'permission_callback' => array($this, 'check_permission')
));
// 保存图片
register_rest_route('wpie/v1', '/save', array(
'methods' => 'POST',
'callback' => array($this, 'save_image'),
'permission_callback' => array($this, 'check_permission')
));
// 批量处理
register_rest_route('wpie/v1', '/batch-process', array(
'methods' => 'POST',
'callback' => array($this, 'batch_process'),
'permission_callback' => array($this, 'check_permission')
));
}
public function check_permission() {
return current_user_can('upload_files');
}
public function handle_upload($request) {
$files = $request->get_file_params();
if (empty($files['image'])) {
return new WP_Error('no_file', '没有上传文件', array('status' => 400));
}
$file = $files['image'];
// 检查文件类型
$allowed_types = array('jpg', 'jpeg', 'png', 'gif', 'webp');
$file_ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($file_ext, $allowed_types)) {
return new WP_Error('invalid_type', '不支持的文件类型', array('status' => 400));
}
// 检查文件大小
$max_size = get_option('wpie_settings')['max_file_size'] * 1024 * 1024;
if ($file['size'] > $max_size) {
return new WP_Error('file_too_large', '文件太大', array('status' => 400));
}
// 创建临时目录
$upload_dir = wp_upload_dir();
$temp_dir = $upload_dir['basedir'] . '/' . WPIE_UPLOAD_DIR . '/temp';
if (!file_exists($temp_dir)) {
wp_mkdir_p($temp_dir);
}
// 生成唯一文件名
$filename = uniqid() . '.' . $file_ext;
$temp_path = $temp_dir . '/' . $filename;
// 移动文件
if (move_uploaded_file($file['tmp_name'], $temp_path)) {
// 创建缩略图
$thumbnail_path = $temp_dir . '/thumb_' . $filename;
$this->create_thumbnail($temp_path, $thumbnail_path, 300, 300);
return rest_ensure_response(array(
'success' => true,
'data' => array(
'original' => basename($temp_path),
'thumbnail' => basename($thumbnail_path),
'url' => $upload_dir['baseurl'] . '/' . WPIE_UPLOAD_DIR . '/temp/' . basename($temp_path),
'thumb_url' => $upload_dir['baseurl'] . '/' . WPIE_UPLOAD_DIR . '/temp/' . basename($thumbnail_path),
'size' => filesize($temp_path),
'dimensions' => getimagesize($temp_path)
)
));
}
return new WP_Error('upload_failed', '上传失败', array('status' => 500));
}
private function create_thumbnail($source_path, $dest_path, $width, $height) {
$processor = new WPIE_Image_Processor($source_path);
$processor->resize($width, $height, true);
$processor->save($dest_path, 80);
}
public function handle_process($request) {
$params = $request->get_json_params();
if (empty($params['image']) || empty($params['operations'])) {
return new WP_Error('missing_params', '缺少必要参数', array('status' => 400));
}
$upload_dir = wp_upload_dir();
$image_path = $upload_dir['basedir'] . '/' . WPIE_UPLOAD_DIR . '/temp/' . $params['image'];
if (!file_exists($image_path)) {
return new WP_Error('file_not_found', '文件不存在', array('status' => 404));
}
try {
$processor = new WPIE_Image_Processor($image_path);
foreach ($params['operations'] as $operation) {
switch ($operation['type']) {
case 'resize':
$processor->resize(
$operation['width'],
$operation['height'],
$operation['keepAspectRatio'] ?? true
);
break;
case 'crop':
$processor->crop(
$operation['x'],
$operation['y'],
$operation['width'],
$operation['height']
);
break;
case 'rotate':
$processor->rotate($operation['degrees']);
break;
case 'brightness':
$processor->adjust_brightness($operation['value']);
break;
case 'contrast':
$processor->adjust_contrast($operation['value']);
break;
}
}
// 生成处理后的临时文件
$output_filename = 'processed_' . uniqid() . '.png';
$output_path = dirname($image_path) . '/' . $output_filename;
$processor->save($output_path);
return rest_ensure_response(array(
'success' => true,
'data' => array(
'filename' => $output_filename,
'url' => $upload_dir['baseurl'] . '/' . WPIE_UPLOAD_DIR . '/temp/' . $output_filename,
'data_url' => $processor->get_image_data()
)
));
} catch (Exception $e) {
return new WP_Error('processing_error', $e->getMessage(), array('status' => 500));
}
}
public function apply_filter($request) {
$params = $request->get_json_params();
if (empty($params['image']) || empty($params['filter'])) {
return new WP_Error('missing_params', '缺少必要参数', array('status' => 400));
}
$upload_dir = wp_upload_dir();
$image_path = $upload_dir['basedir'] . '/' . WPIE_UPLOAD_DIR . '/temp/' . $params['image'];
if (!file_exists($image_path)) {
return new WP_Error('file_not_found', '文件不存在', array('status' => 404));
}
try {
$processor = new WPIE_Image_Processor($image_path);
$processor->apply_filter($params['filter'], $params['args'] ?? []);
$output_filename = 'filtered_' . uniqid() . '.png';
$output_path = dirname($image_path) . '/' . $output_filename;
$processor->save($output_path);
return rest_ensure_response(array(
'success' => true,
'data' => array(
'filename' => $output_filename,
'url' => $upload_dir['baseurl'] . '/' . WPIE_UPLOAD_DIR . '/temp/' . $output_filename,
'data_url' => $processor->get_image_data()
)
));
} catch (Exception $e) {
return new WP_Error('filter_error', $e->getMessage(), array('status' => 500));
}
}
public function save_image($request) {
$params = $request->get_json_params();
if (empty($params['image']) || empty($params['filename'])) {
return new WP_Error('missing_params', '缺少必要参数', array('status' => 400));
}
$upload_dir = wp_upload_dir();
$temp_path = $upload_dir['basedir'] . '/' . WPIE_UPLOAD_DIR . '/temp/' . $params['image'];
if (!file_exists($temp_path)) {
return new WP_Error('file_not_found', '文件不存在', array('status' => 404));
}
// 准备保存到媒体库
$filename = sanitize_file_name($params['filename']);
$file_content = file_get_contents($temp_path);
$upload = wp_upload_bits($filename, null, $file_content);
if ($upload['error']) {
return new WP_Error('save_failed', $upload['error'], array('status' => 500));
}
// 添加到媒体库
$attachment = array(
'post_mime_type' => mime_content_type($upload['file']),
'post_title' => preg_replace('/.[^.]+$/', '', $filename),
'post_content' => '',
'post_status' => 'inherit'
);
$attach_id = wp_insert_attachment($attachment, $upload['file']);
if (is_wp_error($attach_id)) {
return $attach_id;
}
// 生成元数据
require_once(ABSPATH . 'wp-admin/includes/image.php');
$attach_data = wp_generate_attachment_metadata($attach_id, $upload['file']);
wp_update_attachment_metadata($attach_id, $attach_data);
// 清理临时文件
unlink($temp_path);
return rest_ensure_response(array(
'success' => true,
'data' => array(
'id' => $attach_id,
'url' => wp_get_attachment_url($attach_id),
'edit_url' => get_edit_post_link($attach_id, '')
)
));
}
