文章目录
-
- 在当今视觉内容主导的互联网环境中,图片已成为网站内容不可或缺的组成部分。无论是博客文章、产品展示还是社交媒体分享,高质量的图片都能显著提升用户体验和内容吸引力。然而,许多网站运营者面临一个共同挑战:用户上传的图片往往需要调整大小、裁剪、添加水印或进行简单美化,而传统的解决方案要么功能过于复杂,要么需要跳转到外部工具,导致用户体验中断。 WordPress作为全球最流行的内容管理系统,拥有强大的扩展能力。通过代码二次开发,我们可以在WordPress网站中嵌入一个轻量级的在线图片编辑与美化工具,让用户在不离开网站的情况下完成基本的图片处理操作。这不仅提升了用户体验,还能增加用户粘性和内容生产效率。 本文将详细介绍如何通过WordPress代码二次开发,实现一个功能完善但操作简单的内嵌图片编辑工具,涵盖从需求分析、技术选型到具体实现的完整流程。
-
- 在开始开发之前,我们需要明确工具应具备的核心功能: 基础编辑功能: 图片裁剪与调整大小 旋转与翻转 亮度、对比度、饱和度调整 锐化与模糊效果 美化增强功能: 滤镜与特效应用 文字添加与样式设置 贴图与形状叠加 边框与阴影效果 实用工具: 图片压缩与格式转换 水印添加与管理 批量处理基础功能 撤销/重做操作 用户体验需求: 响应式设计,适配不同设备 直观的操作界面 实时预览效果 快速导出与保存
- 基于WordPress平台,我们有多种技术方案可选: 前端技术栈: HTML5 Canvas:用于图片处理的核心技术 JavaScript(ES6+):实现交互逻辑 CSS3:界面样式与动画效果 图片处理库选择: Fabric.js:功能强大的Canvas库,适合交互式图片编辑 Caman.js:专注于滤镜和颜色调整 原生Canvas API:更轻量,但开发复杂度较高 WordPress集成方案: 短代码(Shortcode)嵌入 Gutenberg块编辑器集成 独立管理页面 媒体库扩展 综合考虑开发效率与功能需求,我们选择Fabric.js作为核心图片处理库,通过短代码和媒体库扩展的方式集成到WordPress中。
-
- 本地WordPress环境: 安装Local by Flywheel或XAMPP 配置PHP 7.4+环境 确保启用GD库和ImageMagick扩展 代码编辑器准备: VS Code或PHPStorm 安装WordPress开发相关插件 版本控制: 初始化Git仓库 建立合理的分支管理策略
- 创建插件目录结构: wp-image-editor-tool/ ├── wp-image-editor-tool.php # 主插件文件 ├── includes/ # 核心功能文件 │ ├── class-editor-core.php # 编辑器核心类 │ ├── class-image-processor.php # 图片处理类 │ └── class-ajax-handler.php # AJAX处理类 ├── admin/ # 后台管理文件 │ ├── css/ │ ├── js/ │ └── class-admin-settings.php ├── public/ # 前端文件 │ ├── css/ │ ├── js/ │ └── templates/ ├── assets/ # 静态资源 │ ├── fonts/ │ ├── icons/ │ └── images/ └── vendor/ # 第三方库 └── fabric.js/
- <?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('WPIET_VERSION', '1.0.0'); define('WPIET_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('WPIET_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 require_once WPIET_PLUGIN_DIR . 'includes/class-plugin-init.php';
-
- 创建编辑器核心类,初始化Fabric.js画布: // public/js/editor-core.js class ImageEditor { constructor(canvasId, options = {}) { this.canvas = new fabric.Canvas(canvasId, { backgroundColor: '#f5f5f5', preserveObjectStacking: true, ...options }); this.history = []; this.historyIndex = -1; this.currentImage = null; this.initEvents(); } initEvents() { // 保存操作历史 this.canvas.on('object:modified', () => this.saveState()); this.canvas.on('object:added', () => this.saveState()); this.canvas.on('object:removed', () => this.saveState()); } saveState() { // 限制历史记录数量 if (this.history.length > 20) { this.history.shift(); } this.history.push(JSON.stringify(this.canvas.toJSON())); this.historyIndex = this.history.length - 1; } loadImage(url) { return new Promise((resolve, reject) => { fabric.Image.fromURL(url, (img) => { this.canvas.clear(); this.canvas.setBackgroundImage(img, this.canvas.renderAll.bind(this.canvas), { scaleX: this.canvas.width / img.width, scaleY: this.canvas.height / img.height }); this.currentImage = img; this.saveState(); resolve(img); }, { crossOrigin: 'anonymous' }); }); } }
-
- class CropTool { constructor(editor) { this.editor = editor; this.isCropping = false; this.cropRect = null; } startCrop() { this.isCropping = true; this.cropRect = new fabric.Rect({ left: 100, top: 100, width: 200, height: 200, fill: 'rgba(0,0,0,0.3)', stroke: '#ffffff', strokeWidth: 2, strokeDashArray: [5, 5], selectable: true, hasControls: true, hasBorders: true }); this.editor.canvas.add(this.cropRect); this.editor.canvas.setActiveObject(this.cropRect); } applyCrop() { if (!this.cropRect || !this.editor.currentImage) return; const canvas = this.editor.canvas; const rect = this.cropRect; // 计算裁剪区域 const scaleX = this.editor.currentImage.scaleX; const scaleY = this.editor.currentImage.scaleY; const cropData = { left: (rect.left - this.editor.currentImage.left) / scaleX, top: (rect.top - this.editor.currentImage.top) / scaleY, width: rect.width / scaleX, height: rect.height / scaleY }; // 创建临时canvas进行裁剪 const tempCanvas = document.createElement('canvas'); const tempCtx = tempCanvas.getContext('2d'); tempCanvas.width = cropData.width; tempCanvas.height = cropData.height; tempCtx.drawImage( canvas.lowerCanvasEl, cropData.left, cropData.top, cropData.width, cropData.height, 0, 0, cropData.width, cropData.height ); // 加载裁剪后的图片 fabric.Image.fromURL(tempCanvas.toDataURL(), (img) => { canvas.clear(); canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); this.editor.currentImage = img; this.editor.saveState(); this.isCropping = false; this.cropRect = null; }); } }
- class AdjustmentTool { constructor(editor) { this.editor = editor; } adjustBrightness(value) { const filters = this.editor.currentImage.filters || []; const brightnessFilter = new fabric.Image.filters.Brightness({ brightness: value / 100 }); // 查找并更新或添加亮度滤镜 const existingIndex = filters.findIndex(f => f.type === 'Brightness'); if (existingIndex >= 0) { filters[existingIndex] = brightnessFilter; } else { filters.push(brightnessFilter); } this.applyFilters(filters); } adjustContrast(value) { const filters = this.editor.currentImage.filters || []; const contrastFilter = new fabric.Image.filters.Contrast({ contrast: value / 100 }); const existingIndex = filters.findIndex(f => f.type === 'Contrast'); if (existingIndex >= 0) { filters[existingIndex] = contrastFilter; } else { filters.push(contrastFilter); } this.applyFilters(filters); } applyFilters(filters) { this.editor.currentImage.filters = filters; this.editor.currentImage.applyFilters(); this.editor.canvas.renderAll(); this.editor.saveState(); } }
-
- class FilterSystem { constructor(editor) { this.editor = editor; this.presets = { vintage: { brightness: -0.05, saturation: 0.1, sepia: 0.3 }, blackWhite: { saturation: -1 }, cool: { brightness: 0.05, saturation: 0.2, tint: { color: '#0099ff', opacity: 0.1 } }, warm: { brightness: 0.05, saturation: 0.1, tint: { color: '#ff9900', opacity: 0.1 } } }; } applyPreset(presetName) { const preset = this.presets[presetName]; if (!preset) return; const filters = []; if (preset.brightness !== undefined) { filters.push(new fabric.Image.filters.Brightness({ brightness: preset.brightness })); } if (preset.saturation !== undefined) { filters.push(new fabric.Image.filters.Saturation({ saturation: preset.saturation })); } if (preset.sepia !== undefined) { filters.push(new fabric.Image.filters.Sepia({ amount: preset.sepia })); } if (preset.tint) { filters.push(new fabric.Image.filters.BlendColor({ color: preset.tint.color, mode: 'tint', alpha: preset.tint.opacity })); } this.editor.currentImage.filters = filters; this.editor.currentImage.applyFilters(); this.editor.canvas.renderAll(); this.editor.saveState(); } applyCustomFilter(filterConfig) { // 实现自定义滤镜组合 const filters = []; Object.keys(filterConfig).forEach(key => { switch(key) { case 'brightness': filters.push(new fabric.Image.filters.Brightness(filterConfig[key])); break; case 'contrast': filters.push(new fabric.Image.filters.Contrast(filterConfig[key])); break; case 'saturation': filters.push(new fabric.Image.filters.Saturation(filterConfig[key])); break; // 更多滤镜类型... } }); this.editor.currentImage.filters = filters; this.editor.currentImage.applyFilters(); this.editor.canvas.renderAll(); this.editor.saveState(); } }
- class TextTool { constructor(editor) { this.editor = editor; this.defaultStyles = { fontSize: 24, fontFamily: 'Arial', fill: '#000000', fontWeight: 'normal', textAlign: 'left' }; } addText(content, options = {}) { const textOptions = { ...this.defaultStyles, ...options, left: this.editor.canvas.width / 2, top: this.editor.canvas.height / 2, editable: true }; const text = new fabric.Textbox(content, textOptions); text.setControlsVisibility({ mt: false, // 隐藏上中控制点 mb: false // 隐藏下中控制点 }); this.editor.canvas.add(text); this.editor.canvas.setActiveObject(text); this.editor.saveState(); return text; } updateTextStyle(textObject, styles) { textObject.set(styles); this.editor.canvas.renderAll(); this.editor.saveState(); } addTextShadow(textObject, shadowConfig) { textObject.set({ shadow: new fabric.Shadow({ color: shadowConfig.color || 'rgba(0,0,0,0.5)', blur: shadowConfig.blur || 5, offsetX: shadowConfig.offsetX || 2, offsetY: shadowConfig.offsetY || 2 }) }); this.editor.canvas.renderAll(); this.editor.saveState(); } }
-
- 创建短代码,让用户可以在文章或页面中嵌入图片编辑器: // includes/class-shortcode-handler.php class WPIET_Shortcode_Handler { public static function init() { add_shortcode('wp_image_editor', [__CLASS__, 'render_editor']); } public static function render_editor($atts) { $atts = shortcode_atts([ 'width' => '800', 'height' => '600', 'image_id' => '', 'toolbar' => 'basic' ], $atts); // 加载必要资源 wp_enqueue_style('wpiet-editor-style'); wp_enqueue_script('fabric-js'); wp_enqueue_script('wpiet-editor-script'); // 获取图片URL $image_url = ''; if (!empty($atts['image_id'])) { $image_url = wp_get_attachment_url($atts['image_id']); } // 渲染编辑器HTML ob_start(); ?> <div class="wpiet-editor-container" data-config="<?php echo esc_attr(json_encode($atts)); ?>"> <div class="wpiet-toolbar"> <!-- 工具栏内容 --> </div> <div class="wpiet-canvas-container"> <canvas id="wpiet-canvas" width="<?php echo esc_attr($atts['width']); ?>" height="<?php echo esc_attr($atts['height']); ?>"> </canvas> </div> <div class="wpiet-sidebar"> <!-- 侧边栏工具 --> </div> <div class="wpiet-controls"> <button class="wpiet-btn-save">保存图片</button> <button class="wpiet-btn-reset">重置</button> </div> </div> <?php return ob_get_clean(); } }
- 扩展WordPress媒体库,添加"编辑图片"选项: // includes/class-media-library-integration.php class WPIET_Media_Library_Integration { public static function init() { // 在媒体库列表添加编辑链接 add_filter('media_row_actions', [__CLASS__, 'add_edit_action'], 10, 2); // 在附件详情页添加编辑按钮 add_action('attachment_submitbox_misc_actions', [__CLASS__, 'add_edit_button']); // 添加媒体库模态框中的编辑选项 add_action('print_media_templates', [__CLASS__, 'add_media_template']); } public static function add_edit_action($actions, $post) { if (wp_attachment_is_image($post)) { $edit_url = admin_url('admin.php?page=wpiet-edit&image_id=' . $post->ID); $actions['wpiet_edit'] = sprintf( '<a href="%s" target="_blank">%s</a>', esc_url($edit_url), __('编辑图片', 'wp-image-editor') ); } return $actions; } public static function add_edit_button() { global $post; if (!wp_attachment_is_image($post->ID)) { return; } ?> <div class="misc-pub-section"> <a href="<?php echo admin_url('admin.php?page=wpiet-edit&image_id=' . $post->ID); ?>" class="button button-large" target="_blank">
-
-
- // includes/class-image-processor.php class WPIET_Image_Processor { private $allowed_mime_types = [ 'image/jpeg', 'image/png', 'image/gif', 'image/webp' ]; private $max_file_size = 5242880; // 5MB public function process_image($image_data, $operations = []) { // 验证图片数据 if (!$this->validate_image_data($image_data)) { return new WP_Error('invalid_image', '无效的图片数据'); } // 创建临时文件 $temp_file = $this->create_temp_file($image_data); if (is_wp_error($temp_file)) { return $temp_file; } // 应用图片操作 $processed_image = $this->apply_operations($temp_file, $operations); // 清理临时文件 unlink($temp_file); return $processed_image; } private function validate_image_data($image_data) { // 检查数据格式 if (!is_string($image_data) || empty($image_data)) { return false; } // 检查是否为有效的base64或URL if (strpos($image_data, 'data:image') === 0) { // base64格式 $parts = explode(',', $image_data); if (count($parts) !== 2) { return false; } // 解码并验证 $decoded = base64_decode($parts[1]); if ($decoded === false) { return false; } // 检查文件大小 if (strlen($decoded) > $this->max_file_size) { return false; } // 检查MIME类型 $finfo = finfo_open(FILEINFO_MIME_TYPE); $mime_type = finfo_buffer($finfo, $decoded); finfo_close($finfo); if (!in_array($mime_type, $this->allowed_mime_types)) { return false; } } return true; } private function create_temp_file($image_data) { $temp_dir = get_temp_dir(); $temp_file = tempnam($temp_dir, 'wpiet_'); if (strpos($image_data, 'data:image') === 0) { // base64数据 $parts = explode(',', $image_data); $image_binary = base64_decode($parts[1]); file_put_contents($temp_file, $image_binary); } else { // URL或文件路径 $response = wp_remote_get($image_data); if (is_wp_error($response)) { return $response; } $image_binary = wp_remote_retrieve_body($response); file_put_contents($temp_file, $image_binary); } return $temp_file; } private function apply_operations($image_path, $operations) { $editor = wp_get_image_editor($image_path); if (is_wp_error($editor)) { return $editor; } // 应用各项操作 foreach ($operations as $operation) { switch ($operation['type']) { case 'crop': $editor->crop( $operation['x'], $operation['y'], $operation['width'], $operation['height'] ); break; case 'resize': $editor->resize( $operation['width'], $operation['height'], $operation['crop'] ?? false ); break; case 'rotate': $editor->rotate($operation['angle']); break; case 'flip': $editor->flip( $operation['direction'] === 'horizontal' ? 'horiz' : 'vert' ); break; case 'filter': $this->apply_filter($editor, $operation); break; } } // 生成新文件名 $filename = 'edited-' . time() . '-' . wp_basename($image_path); $upload_dir = wp_upload_dir(); $file_path = $upload_dir['path'] . '/' . $filename; // 保存图片 $result = $editor->save($file_path); if (is_wp_error($result)) { return $result; } return [ 'path' => $result['path'], 'url' => $upload_dir['url'] . '/' . $result['file'], 'width' => $result['width'], 'height' => $result['height'], 'size' => filesize($result['path']) ]; } private function apply_filter($editor, $filter) { // 使用ImageMagick或GD应用滤镜 $image_path = $editor->get_file(); if (extension_loaded('imagick')) { $this->apply_imagick_filter($image_path, $filter); } else { $this->apply_gd_filter($image_path, $filter); } } private function apply_imagick_filter($image_path, $filter) { $imagick = new Imagick($image_path); switch ($filter['name']) { case 'brightness': $imagick->modulateImage( $filter['value'] + 100, 100, 100 ); break; case 'contrast': $imagick->sigmoidalContrastImage( true, $filter['value'] / 10, 0 ); break; case 'saturation': $imagick->modulateImage( 100, $filter['value'] + 100, 100 ); break; case 'sepia': $imagick->sepiaToneImage($filter['value']); break; case 'blur': $imagick->gaussianBlurImage( $filter['radius'], $filter['sigma'] ); break; } $imagick->writeImage($image_path); $imagick->destroy(); } }
- // includes/class-ajax-handler.php class WPIET_Ajax_Handler { public static function init() { // 保存图片 add_action('wp_ajax_wpiet_save_image', [__CLASS__, 'save_image']); add_action('wp_ajax_nopriv_wpiet_save_image', [__CLASS__, 'save_image_nopriv']); // 获取图片信息 add_action('wp_ajax_wpiet_get_image_info', [__CLASS__, 'get_image_info']); // 批量处理 add_action('wp_ajax_wpiet_batch_process', [__CLASS__, 'batch_process']); } public static function save_image() { // 验证nonce if (!check_ajax_referer('wpiet_editor_nonce', 'nonce', false)) { wp_die('安全验证失败', 403); } // 验证权限 if (!current_user_can('upload_files')) { wp_die('权限不足', 403); } // 获取数据 $image_data = isset($_POST['image_data']) ? $_POST['image_data'] : ''; $operations = isset($_POST['operations']) ? json_decode(stripslashes($_POST['operations']), true) : []; $filename = isset($_POST['filename']) ? sanitize_file_name($_POST['filename']) : ''; if (empty($image_data)) { wp_send_json_error('没有图片数据'); } // 处理图片 $processor = new WPIET_Image_Processor(); $result = $processor->process_image($image_data, $operations); if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); } // 创建媒体库附件 $attachment_id = self::create_attachment($result['path'], $filename); if (is_wp_error($attachment_id)) { wp_send_json_error($attachment_id->get_error_message()); } // 返回结果 wp_send_json_success([ 'attachment_id' => $attachment_id, 'url' => $result['url'], 'edit_url' => get_edit_post_link($attachment_id), 'size' => size_format($result['size']) ]); } public static function save_image_nopriv() { // 非登录用户处理 if (!get_option('wpiet_allow_guest_upload', false)) { wp_die('请登录后操作', 403); } // 验证reCAPTCHA(如果启用) if (get_option('wpiet_enable_recaptcha', false)) { $recaptcha_response = isset($_POST['g-recaptcha-response']) ? $_POST['g-recaptcha-response'] : ''; if (!self::verify_recaptcha($recaptcha_response)) { wp_send_json_error('验证码验证失败'); } } // 继续处理图片 self::save_image(); } private static function create_attachment($file_path, $filename) { $file_type = wp_check_filetype($filename, null); $attachment = [ 'post_mime_type' => $file_type['type'], 'post_title' => preg_replace('/.[^.]+$/', '', $filename), 'post_content' => '', 'post_status' => 'inherit', 'guid' => wp_get_upload_dir()['url'] . '/' . $filename ]; $attachment_id = wp_insert_attachment($attachment, $file_path); if (is_wp_error($attachment_id)) { return $attachment_id; } // 生成附件元数据 require_once(ABSPATH . 'wp-admin/includes/image.php'); $attachment_data = wp_generate_attachment_metadata($attachment_id, $file_path); wp_update_attachment_metadata($attachment_id, $attachment_data); return $attachment_id; } private static function verify_recaptcha($response) { $secret_key = get_option('wpiet_recaptcha_secret_key', ''); if (empty($secret_key)) { return true; } $verify_url = 'https://www.google.com/recaptcha/api/siteverify'; $verify_data = [ 'secret' => $secret_key, 'response' => $response, 'remoteip' => $_SERVER['REMOTE_ADDR'] ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $verify_url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($verify_data)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); $result = curl_exec($ch); curl_close($ch); $result_data = json_decode($result, true); return isset($result_data['success']) && $result_data['success'] === true; } }
-
在当今视觉内容主导的互联网环境中,图片已成为网站内容不可或缺的组成部分。无论是博客文章、产品展示还是社交媒体分享,高质量的图片都能显著提升用户体验和内容吸引力。然而,许多网站运营者面临一个共同挑战:用户上传的图片往往需要调整大小、裁剪、添加水印或进行简单美化,而传统的解决方案要么功能过于复杂,要么需要跳转到外部工具,导致用户体验中断。
WordPress作为全球最流行的内容管理系统,拥有强大的扩展能力。通过代码二次开发,我们可以在WordPress网站中嵌入一个轻量级的在线图片编辑与美化工具,让用户在不离开网站的情况下完成基本的图片处理操作。这不仅提升了用户体验,还能增加用户粘性和内容生产效率。
本文将详细介绍如何通过WordPress代码二次开发,实现一个功能完善但操作简单的内嵌图片编辑工具,涵盖从需求分析、技术选型到具体实现的完整流程。
在开始开发之前,我们需要明确工具应具备的核心功能:
-
基础编辑功能:
- 图片裁剪与调整大小
- 旋转与翻转
- 亮度、对比度、饱和度调整
- 锐化与模糊效果
-
美化增强功能:
- 滤镜与特效应用
- 文字添加与样式设置
- 贴图与形状叠加
- 边框与阴影效果
-
实用工具:
- 图片压缩与格式转换
- 水印添加与管理
- 批量处理基础功能
- 撤销/重做操作
-
用户体验需求:
- 响应式设计,适配不同设备
- 直观的操作界面
- 实时预览效果
- 快速导出与保存
基于WordPress平台,我们有多种技术方案可选:
-
前端技术栈:
- HTML5 Canvas:用于图片处理的核心技术
- JavaScript(ES6+):实现交互逻辑
- CSS3:界面样式与动画效果
-
图片处理库选择:
- Fabric.js:功能强大的Canvas库,适合交互式图片编辑
- Caman.js:专注于滤镜和颜色调整
- 原生Canvas API:更轻量,但开发复杂度较高
-
WordPress集成方案:
- 短代码(Shortcode)嵌入
- Gutenberg块编辑器集成
- 独立管理页面
- 媒体库扩展
综合考虑开发效率与功能需求,我们选择Fabric.js作为核心图片处理库,通过短代码和媒体库扩展的方式集成到WordPress中。
-
本地WordPress环境:
- 安装Local by Flywheel或XAMPP
- 配置PHP 7.4+环境
- 确保启用GD库和ImageMagick扩展
-
代码编辑器准备:
- VS Code或PHPStorm
- 安装WordPress开发相关插件
-
版本控制:
- 初始化Git仓库
- 建立合理的分支管理策略
本地WordPress环境:
- 安装Local by Flywheel或XAMPP
- 配置PHP 7.4+环境
- 确保启用GD库和ImageMagick扩展
代码编辑器准备:
- VS Code或PHPStorm
- 安装WordPress开发相关插件
版本控制:
- 初始化Git仓库
- 建立合理的分支管理策略
创建插件目录结构:
wp-image-editor-tool/
├── wp-image-editor-tool.php # 主插件文件
├── includes/ # 核心功能文件
│ ├── class-editor-core.php # 编辑器核心类
│ ├── class-image-processor.php # 图片处理类
│ └── class-ajax-handler.php # AJAX处理类
├── admin/ # 后台管理文件
│ ├── css/
│ ├── js/
│ └── class-admin-settings.php
├── public/ # 前端文件
│ ├── css/
│ ├── js/
│ └── templates/
├── assets/ # 静态资源
│ ├── fonts/
│ ├── icons/
│ └── images/
└── vendor/ # 第三方库
└── fabric.js/
<?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('WPIET_VERSION', '1.0.0');
define('WPIET_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WPIET_PLUGIN_URL', plugin_dir_url(__FILE__));
// 初始化插件
require_once WPIET_PLUGIN_DIR . 'includes/class-plugin-init.php';
<?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('WPIET_VERSION', '1.0.0');
define('WPIET_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WPIET_PLUGIN_URL', plugin_dir_url(__FILE__));
// 初始化插件
require_once WPIET_PLUGIN_DIR . 'includes/class-plugin-init.php';
创建编辑器核心类,初始化Fabric.js画布:
// public/js/editor-core.js
class ImageEditor {
constructor(canvasId, options = {}) {
this.canvas = new fabric.Canvas(canvasId, {
backgroundColor: '#f5f5f5',
preserveObjectStacking: true,
...options
});
this.history = [];
this.historyIndex = -1;
this.currentImage = null;
this.initEvents();
}
initEvents() {
// 保存操作历史
this.canvas.on('object:modified', () => this.saveState());
this.canvas.on('object:added', () => this.saveState());
this.canvas.on('object:removed', () => this.saveState());
}
saveState() {
// 限制历史记录数量
if (this.history.length > 20) {
this.history.shift();
}
this.history.push(JSON.stringify(this.canvas.toJSON()));
this.historyIndex = this.history.length - 1;
}
loadImage(url) {
return new Promise((resolve, reject) => {
fabric.Image.fromURL(url, (img) => {
this.canvas.clear();
this.canvas.setBackgroundImage(img, this.canvas.renderAll.bind(this.canvas), {
scaleX: this.canvas.width / img.width,
scaleY: this.canvas.height / img.height
});
this.currentImage = img;
this.saveState();
resolve(img);
}, {
crossOrigin: 'anonymous'
});
});
}
}
class CropTool {
constructor(editor) {
this.editor = editor;
this.isCropping = false;
this.cropRect = null;
}
startCrop() {
this.isCropping = true;
this.cropRect = new fabric.Rect({
left: 100,
top: 100,
width: 200,
height: 200,
fill: 'rgba(0,0,0,0.3)',
stroke: '#ffffff',
strokeWidth: 2,
strokeDashArray: [5, 5],
selectable: true,
hasControls: true,
hasBorders: true
});
this.editor.canvas.add(this.cropRect);
this.editor.canvas.setActiveObject(this.cropRect);
}
applyCrop() {
if (!this.cropRect || !this.editor.currentImage) return;
const canvas = this.editor.canvas;
const rect = this.cropRect;
// 计算裁剪区域
const scaleX = this.editor.currentImage.scaleX;
const scaleY = this.editor.currentImage.scaleY;
const cropData = {
left: (rect.left - this.editor.currentImage.left) / scaleX,
top: (rect.top - this.editor.currentImage.top) / scaleY,
width: rect.width / scaleX,
height: rect.height / scaleY
};
// 创建临时canvas进行裁剪
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = cropData.width;
tempCanvas.height = cropData.height;
tempCtx.drawImage(
canvas.lowerCanvasEl,
cropData.left, cropData.top,
cropData.width, cropData.height,
0, 0,
cropData.width, cropData.height
);
// 加载裁剪后的图片
fabric.Image.fromURL(tempCanvas.toDataURL(), (img) => {
canvas.clear();
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
this.editor.currentImage = img;
this.editor.saveState();
this.isCropping = false;
this.cropRect = null;
});
}
}
class CropTool {
constructor(editor) {
this.editor = editor;
this.isCropping = false;
this.cropRect = null;
}
startCrop() {
this.isCropping = true;
this.cropRect = new fabric.Rect({
left: 100,
top: 100,
width: 200,
height: 200,
fill: 'rgba(0,0,0,0.3)',
stroke: '#ffffff',
strokeWidth: 2,
strokeDashArray: [5, 5],
selectable: true,
hasControls: true,
hasBorders: true
});
this.editor.canvas.add(this.cropRect);
this.editor.canvas.setActiveObject(this.cropRect);
}
applyCrop() {
if (!this.cropRect || !this.editor.currentImage) return;
const canvas = this.editor.canvas;
const rect = this.cropRect;
// 计算裁剪区域
const scaleX = this.editor.currentImage.scaleX;
const scaleY = this.editor.currentImage.scaleY;
const cropData = {
left: (rect.left - this.editor.currentImage.left) / scaleX,
top: (rect.top - this.editor.currentImage.top) / scaleY,
width: rect.width / scaleX,
height: rect.height / scaleY
};
// 创建临时canvas进行裁剪
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = cropData.width;
tempCanvas.height = cropData.height;
tempCtx.drawImage(
canvas.lowerCanvasEl,
cropData.left, cropData.top,
cropData.width, cropData.height,
0, 0,
cropData.width, cropData.height
);
// 加载裁剪后的图片
fabric.Image.fromURL(tempCanvas.toDataURL(), (img) => {
canvas.clear();
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
this.editor.currentImage = img;
this.editor.saveState();
this.isCropping = false;
this.cropRect = null;
});
}
}
class AdjustmentTool {
constructor(editor) {
this.editor = editor;
}
adjustBrightness(value) {
const filters = this.editor.currentImage.filters || [];
const brightnessFilter = new fabric.Image.filters.Brightness({
brightness: value / 100
});
// 查找并更新或添加亮度滤镜
const existingIndex = filters.findIndex(f => f.type === 'Brightness');
if (existingIndex >= 0) {
filters[existingIndex] = brightnessFilter;
} else {
filters.push(brightnessFilter);
}
this.applyFilters(filters);
}
adjustContrast(value) {
const filters = this.editor.currentImage.filters || [];
const contrastFilter = new fabric.Image.filters.Contrast({
contrast: value / 100
});
const existingIndex = filters.findIndex(f => f.type === 'Contrast');
if (existingIndex >= 0) {
filters[existingIndex] = contrastFilter;
} else {
filters.push(contrastFilter);
}
this.applyFilters(filters);
}
applyFilters(filters) {
this.editor.currentImage.filters = filters;
this.editor.currentImage.applyFilters();
this.editor.canvas.renderAll();
this.editor.saveState();
}
}
class AdjustmentTool {
constructor(editor) {
this.editor = editor;
}
adjustBrightness(value) {
const filters = this.editor.currentImage.filters || [];
const brightnessFilter = new fabric.Image.filters.Brightness({
brightness: value / 100
});
// 查找并更新或添加亮度滤镜
const existingIndex = filters.findIndex(f => f.type === 'Brightness');
if (existingIndex >= 0) {
filters[existingIndex] = brightnessFilter;
} else {
filters.push(brightnessFilter);
}
this.applyFilters(filters);
}
adjustContrast(value) {
const filters = this.editor.currentImage.filters || [];
const contrastFilter = new fabric.Image.filters.Contrast({
contrast: value / 100
});
const existingIndex = filters.findIndex(f => f.type === 'Contrast');
if (existingIndex >= 0) {
filters[existingIndex] = contrastFilter;
} else {
filters.push(contrastFilter);
}
this.applyFilters(filters);
}
applyFilters(filters) {
this.editor.currentImage.filters = filters;
this.editor.currentImage.applyFilters();
this.editor.canvas.renderAll();
this.editor.saveState();
}
}
class FilterSystem {
constructor(editor) {
this.editor = editor;
this.presets = {
vintage: {
brightness: -0.05,
saturation: 0.1,
sepia: 0.3
},
blackWhite: {
saturation: -1
},
cool: {
brightness: 0.05,
saturation: 0.2,
tint: {
color: '#0099ff',
opacity: 0.1
}
},
warm: {
brightness: 0.05,
saturation: 0.1,
tint: {
color: '#ff9900',
opacity: 0.1
}
}
};
}
applyPreset(presetName) {
const preset = this.presets[presetName];
if (!preset) return;
const filters = [];
if (preset.brightness !== undefined) {
filters.push(new fabric.Image.filters.Brightness({
brightness: preset.brightness
}));
}
if (preset.saturation !== undefined) {
filters.push(new fabric.Image.filters.Saturation({
saturation: preset.saturation
}));
}
if (preset.sepia !== undefined) {
filters.push(new fabric.Image.filters.Sepia({
amount: preset.sepia
}));
}
if (preset.tint) {
filters.push(new fabric.Image.filters.BlendColor({
color: preset.tint.color,
mode: 'tint',
alpha: preset.tint.opacity
}));
}
this.editor.currentImage.filters = filters;
this.editor.currentImage.applyFilters();
this.editor.canvas.renderAll();
this.editor.saveState();
}
applyCustomFilter(filterConfig) {
// 实现自定义滤镜组合
const filters = [];
Object.keys(filterConfig).forEach(key => {
switch(key) {
case 'brightness':
filters.push(new fabric.Image.filters.Brightness(filterConfig[key]));
break;
case 'contrast':
filters.push(new fabric.Image.filters.Contrast(filterConfig[key]));
break;
case 'saturation':
filters.push(new fabric.Image.filters.Saturation(filterConfig[key]));
break;
// 更多滤镜类型...
}
});
this.editor.currentImage.filters = filters;
this.editor.currentImage.applyFilters();
this.editor.canvas.renderAll();
this.editor.saveState();
}
}
class FilterSystem {
constructor(editor) {
this.editor = editor;
this.presets = {
vintage: {
brightness: -0.05,
saturation: 0.1,
sepia: 0.3
},
blackWhite: {
saturation: -1
},
cool: {
brightness: 0.05,
saturation: 0.2,
tint: {
color: '#0099ff',
opacity: 0.1
}
},
warm: {
brightness: 0.05,
saturation: 0.1,
tint: {
color: '#ff9900',
opacity: 0.1
}
}
};
}
applyPreset(presetName) {
const preset = this.presets[presetName];
if (!preset) return;
const filters = [];
if (preset.brightness !== undefined) {
filters.push(new fabric.Image.filters.Brightness({
brightness: preset.brightness
}));
}
if (preset.saturation !== undefined) {
filters.push(new fabric.Image.filters.Saturation({
saturation: preset.saturation
}));
}
if (preset.sepia !== undefined) {
filters.push(new fabric.Image.filters.Sepia({
amount: preset.sepia
}));
}
if (preset.tint) {
filters.push(new fabric.Image.filters.BlendColor({
color: preset.tint.color,
mode: 'tint',
alpha: preset.tint.opacity
}));
}
this.editor.currentImage.filters = filters;
this.editor.currentImage.applyFilters();
this.editor.canvas.renderAll();
this.editor.saveState();
}
applyCustomFilter(filterConfig) {
// 实现自定义滤镜组合
const filters = [];
Object.keys(filterConfig).forEach(key => {
switch(key) {
case 'brightness':
filters.push(new fabric.Image.filters.Brightness(filterConfig[key]));
break;
case 'contrast':
filters.push(new fabric.Image.filters.Contrast(filterConfig[key]));
break;
case 'saturation':
filters.push(new fabric.Image.filters.Saturation(filterConfig[key]));
break;
// 更多滤镜类型...
}
});
this.editor.currentImage.filters = filters;
this.editor.currentImage.applyFilters();
this.editor.canvas.renderAll();
this.editor.saveState();
}
}
class TextTool {
constructor(editor) {
this.editor = editor;
this.defaultStyles = {
fontSize: 24,
fontFamily: 'Arial',
fill: '#000000',
fontWeight: 'normal',
textAlign: 'left'
};
}
addText(content, options = {}) {
const textOptions = {
...this.defaultStyles,
...options,
left: this.editor.canvas.width / 2,
top: this.editor.canvas.height / 2,
editable: true
};
const text = new fabric.Textbox(content, textOptions);
text.setControlsVisibility({
mt: false, // 隐藏上中控制点
mb: false // 隐藏下中控制点
});
this.editor.canvas.add(text);
this.editor.canvas.setActiveObject(text);
this.editor.saveState();
return text;
}
updateTextStyle(textObject, styles) {
textObject.set(styles);
this.editor.canvas.renderAll();
this.editor.saveState();
}
addTextShadow(textObject, shadowConfig) {
textObject.set({
shadow: new fabric.Shadow({
color: shadowConfig.color || 'rgba(0,0,0,0.5)',
blur: shadowConfig.blur || 5,
offsetX: shadowConfig.offsetX || 2,
offsetY: shadowConfig.offsetY || 2
})
});
this.editor.canvas.renderAll();
this.editor.saveState();
}
}
class TextTool {
constructor(editor) {
this.editor = editor;
this.defaultStyles = {
fontSize: 24,
fontFamily: 'Arial',
fill: '#000000',
fontWeight: 'normal',
textAlign: 'left'
};
}
addText(content, options = {}) {
const textOptions = {
...this.defaultStyles,
...options,
left: this.editor.canvas.width / 2,
top: this.editor.canvas.height / 2,
editable: true
};
const text = new fabric.Textbox(content, textOptions);
text.setControlsVisibility({
mt: false, // 隐藏上中控制点
mb: false // 隐藏下中控制点
});
this.editor.canvas.add(text);
this.editor.canvas.setActiveObject(text);
this.editor.saveState();
return text;
}
updateTextStyle(textObject, styles) {
textObject.set(styles);
this.editor.canvas.renderAll();
this.editor.saveState();
}
addTextShadow(textObject, shadowConfig) {
textObject.set({
shadow: new fabric.Shadow({
color: shadowConfig.color || 'rgba(0,0,0,0.5)',
blur: shadowConfig.blur || 5,
offsetX: shadowConfig.offsetX || 2,
offsetY: shadowConfig.offsetY || 2
})
});
this.editor.canvas.renderAll();
this.editor.saveState();
}
}
创建短代码,让用户可以在文章或页面中嵌入图片编辑器:
// includes/class-shortcode-handler.php
class WPIET_Shortcode_Handler {
public static function init() {
add_shortcode('wp_image_editor', [__CLASS__, 'render_editor']);
}
public static function render_editor($atts) {
$atts = shortcode_atts([
'width' => '800',
'height' => '600',
'image_id' => '',
'toolbar' => 'basic'
], $atts);
// 加载必要资源
wp_enqueue_style('wpiet-editor-style');
wp_enqueue_script('fabric-js');
wp_enqueue_script('wpiet-editor-script');
// 获取图片URL
$image_url = '';
if (!empty($atts['image_id'])) {
$image_url = wp_get_attachment_url($atts['image_id']);
}
// 渲染编辑器HTML
ob_start();
?>
<div class="wpiet-editor-container" data-config="<?php echo esc_attr(json_encode($atts)); ?>">
<div class="wpiet-toolbar">
<!-- 工具栏内容 -->
</div>
<div class="wpiet-canvas-container">
<canvas id="wpiet-canvas"
width="<?php echo esc_attr($atts['width']); ?>"
height="<?php echo esc_attr($atts['height']); ?>">
</canvas>
</div>
<div class="wpiet-sidebar">
<!-- 侧边栏工具 -->
</div>
<div class="wpiet-controls">
<button class="wpiet-btn-save">保存图片</button>
<button class="wpiet-btn-reset">重置</button>
</div>
</div>
<?php
return ob_get_clean();
}
}
扩展WordPress媒体库,添加"编辑图片"选项:
// includes/class-media-library-integration.php
class WPIET_Media_Library_Integration {
public static function init() {
// 在媒体库列表添加编辑链接
add_filter('media_row_actions', [__CLASS__, 'add_edit_action'], 10, 2);
// 在附件详情页添加编辑按钮
add_action('attachment_submitbox_misc_actions', [__CLASS__, 'add_edit_button']);
// 添加媒体库模态框中的编辑选项
add_action('print_media_templates', [__CLASS__, 'add_media_template']);
}
public static function add_edit_action($actions, $post) {
if (wp_attachment_is_image($post)) {
$edit_url = admin_url('admin.php?page=wpiet-edit&image_id=' . $post->ID);
$actions['wpiet_edit'] = sprintf(
'<a href="%s" target="_blank">%s</a>',
esc_url($edit_url),
__('编辑图片', 'wp-image-editor')
);
}
return $actions;
}
public static function add_edit_button() {
global $post;
if (!wp_attachment_is_image($post->ID)) {
return;
}
?>
<div class="misc-pub-section">
<a href="<?php echo admin_url('admin.php?page=wpiet-edit&image_id=' . $post->ID); ?>"
class="button button-large"
target="_blank">
// includes/class-image-processor.php
class WPIET_Image_Processor {
private $allowed_mime_types = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp'
];
private $max_file_size = 5242880; // 5MB
public function process_image($image_data, $operations = []) {
// 验证图片数据
if (!$this->validate_image_data($image_data)) {
return new WP_Error('invalid_image', '无效的图片数据');
}
// 创建临时文件
$temp_file = $this->create_temp_file($image_data);
if (is_wp_error($temp_file)) {
return $temp_file;
}
// 应用图片操作
$processed_image = $this->apply_operations($temp_file, $operations);
// 清理临时文件
unlink($temp_file);
return $processed_image;
}
private function validate_image_data($image_data) {
// 检查数据格式
if (!is_string($image_data) || empty($image_data)) {
return false;
}
// 检查是否为有效的base64或URL
if (strpos($image_data, 'data:image') === 0) {
// base64格式
$parts = explode(',', $image_data);
if (count($parts) !== 2) {
return false;
}
// 解码并验证
$decoded = base64_decode($parts[1]);
if ($decoded === false) {
return false;
}
// 检查文件大小
if (strlen($decoded) > $this->max_file_size) {
return false;
}
// 检查MIME类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_buffer($finfo, $decoded);
finfo_close($finfo);
if (!in_array($mime_type, $this->allowed_mime_types)) {
return false;
}
}
return true;
}
private function create_temp_file($image_data) {
$temp_dir = get_temp_dir();
$temp_file = tempnam($temp_dir, 'wpiet_');
if (strpos($image_data, 'data:image') === 0) {
// base64数据
$parts = explode(',', $image_data);
$image_binary = base64_decode($parts[1]);
file_put_contents($temp_file, $image_binary);
} else {
// URL或文件路径
$response = wp_remote_get($image_data);
if (is_wp_error($response)) {
return $response;
}
$image_binary = wp_remote_retrieve_body($response);
file_put_contents($temp_file, $image_binary);
}
return $temp_file;
}
private function apply_operations($image_path, $operations) {
$editor = wp_get_image_editor($image_path);
if (is_wp_error($editor)) {
return $editor;
}
// 应用各项操作
foreach ($operations as $operation) {
switch ($operation['type']) {
case 'crop':
$editor->crop(
$operation['x'],
$operation['y'],
$operation['width'],
$operation['height']
);
break;
case 'resize':
$editor->resize(
$operation['width'],
$operation['height'],
$operation['crop'] ?? false
);
break;
case 'rotate':
$editor->rotate($operation['angle']);
break;
case 'flip':
$editor->flip(
$operation['direction'] === 'horizontal' ? 'horiz' : 'vert'
);
break;
case 'filter':
$this->apply_filter($editor, $operation);
break;
}
}
// 生成新文件名
$filename = 'edited-' . time() . '-' . wp_basename($image_path);
$upload_dir = wp_upload_dir();
$file_path = $upload_dir['path'] . '/' . $filename;
// 保存图片
$result = $editor->save($file_path);
if (is_wp_error($result)) {
return $result;
}
return [
'path' => $result['path'],
'url' => $upload_dir['url'] . '/' . $result['file'],
'width' => $result['width'],
'height' => $result['height'],
'size' => filesize($result['path'])
];
}
private function apply_filter($editor, $filter) {
// 使用ImageMagick或GD应用滤镜
$image_path = $editor->get_file();
if (extension_loaded('imagick')) {
$this->apply_imagick_filter($image_path, $filter);
} else {
$this->apply_gd_filter($image_path, $filter);
}
}
private function apply_imagick_filter($image_path, $filter) {
$imagick = new Imagick($image_path);
switch ($filter['name']) {
case 'brightness':
$imagick->modulateImage(
$filter['value'] + 100,
100,
100
);
break;
case 'contrast':
$imagick->sigmoidalContrastImage(
true,
$filter['value'] / 10,
0
);
break;
case 'saturation':
$imagick->modulateImage(
100,
$filter['value'] + 100,
100
);
break;
case 'sepia':
$imagick->sepiaToneImage($filter['value']);
break;
case 'blur':
$imagick->gaussianBlurImage(
$filter['radius'],
$filter['sigma']
);
break;
}
$imagick->writeImage($image_path);
$imagick->destroy();
}
}
// includes/class-image-processor.php
class WPIET_Image_Processor {
private $allowed_mime_types = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp'
];
private $max_file_size = 5242880; // 5MB
public function process_image($image_data, $operations = []) {
// 验证图片数据
if (!$this->validate_image_data($image_data)) {
return new WP_Error('invalid_image', '无效的图片数据');
}
// 创建临时文件
$temp_file = $this->create_temp_file($image_data);
if (is_wp_error($temp_file)) {
return $temp_file;
}
// 应用图片操作
$processed_image = $this->apply_operations($temp_file, $operations);
// 清理临时文件
unlink($temp_file);
return $processed_image;
}
private function validate_image_data($image_data) {
// 检查数据格式
if (!is_string($image_data) || empty($image_data)) {
return false;
}
// 检查是否为有效的base64或URL
if (strpos($image_data, 'data:image') === 0) {
// base64格式
$parts = explode(',', $image_data);
if (count($parts) !== 2) {
return false;
}
// 解码并验证
$decoded = base64_decode($parts[1]);
if ($decoded === false) {
return false;
}
// 检查文件大小
if (strlen($decoded) > $this->max_file_size) {
return false;
}
// 检查MIME类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_buffer($finfo, $decoded);
finfo_close($finfo);
if (!in_array($mime_type, $this->allowed_mime_types)) {
return false;
}
}
return true;
}
private function create_temp_file($image_data) {
$temp_dir = get_temp_dir();
$temp_file = tempnam($temp_dir, 'wpiet_');
if (strpos($image_data, 'data:image') === 0) {
// base64数据
$parts = explode(',', $image_data);
$image_binary = base64_decode($parts[1]);
file_put_contents($temp_file, $image_binary);
} else {
// URL或文件路径
$response = wp_remote_get($image_data);
if (is_wp_error($response)) {
return $response;
}
$image_binary = wp_remote_retrieve_body($response);
file_put_contents($temp_file, $image_binary);
}
return $temp_file;
}
private function apply_operations($image_path, $operations) {
$editor = wp_get_image_editor($image_path);
if (is_wp_error($editor)) {
return $editor;
}
// 应用各项操作
foreach ($operations as $operation) {
switch ($operation['type']) {
case 'crop':
$editor->crop(
$operation['x'],
$operation['y'],
$operation['width'],
$operation['height']
);
break;
case 'resize':
$editor->resize(
$operation['width'],
$operation['height'],
$operation['crop'] ?? false
);
break;
case 'rotate':
$editor->rotate($operation['angle']);
break;
case 'flip':
$editor->flip(
$operation['direction'] === 'horizontal' ? 'horiz' : 'vert'
);
break;
case 'filter':
$this->apply_filter($editor, $operation);
break;
}
}
// 生成新文件名
$filename = 'edited-' . time() . '-' . wp_basename($image_path);
$upload_dir = wp_upload_dir();
$file_path = $upload_dir['path'] . '/' . $filename;
// 保存图片
$result = $editor->save($file_path);
if (is_wp_error($result)) {
return $result;
}
return [
'path' => $result['path'],
'url' => $upload_dir['url'] . '/' . $result['file'],
'width' => $result['width'],
'height' => $result['height'],
'size' => filesize($result['path'])
];
}
private function apply_filter($editor, $filter) {
// 使用ImageMagick或GD应用滤镜
$image_path = $editor->get_file();
if (extension_loaded('imagick')) {
$this->apply_imagick_filter($image_path, $filter);
} else {
$this->apply_gd_filter($image_path, $filter);
}
}
private function apply_imagick_filter($image_path, $filter) {
$imagick = new Imagick($image_path);
switch ($filter['name']) {
case 'brightness':
$imagick->modulateImage(
$filter['value'] + 100,
100,
100
);
break;
case 'contrast':
$imagick->sigmoidalContrastImage(
true,
$filter['value'] / 10,
0
);
break;
case 'saturation':
$imagick->modulateImage(
100,
$filter['value'] + 100,
100
);
break;
case 'sepia':
$imagick->sepiaToneImage($filter['value']);
break;
case 'blur':
$imagick->gaussianBlurImage(
$filter['radius'],
$filter['sigma']
);
break;
}
$imagick->writeImage($image_path);
$imagick->destroy();
}
}
// includes/class-ajax-handler.php
class WPIET_Ajax_Handler {
public static function init() {
// 保存图片
add_action('wp_ajax_wpiet_save_image', [__CLASS__, 'save_image']);
add_action('wp_ajax_nopriv_wpiet_save_image', [__CLASS__, 'save_image_nopriv']);
// 获取图片信息
add_action('wp_ajax_wpiet_get_image_info', [__CLASS__, 'get_image_info']);
// 批量处理
add_action('wp_ajax_wpiet_batch_process', [__CLASS__, 'batch_process']);
}
public static function save_image() {
// 验证nonce
if (!check_ajax_referer('wpiet_editor_nonce', 'nonce', false)) {
wp_die('安全验证失败', 403);
}
// 验证权限
if (!current_user_can('upload_files')) {
wp_die('权限不足', 403);
}
// 获取数据
$image_data = isset($_POST['image_data']) ? $_POST['image_data'] : '';
$operations = isset($_POST['operations']) ? json_decode(stripslashes($_POST['operations']), true) : [];
$filename = isset($_POST['filename']) ? sanitize_file_name($_POST['filename']) : '';
if (empty($image_data)) {
wp_send_json_error('没有图片数据');
}
// 处理图片
$processor = new WPIET_Image_Processor();
$result = $processor->process_image($image_data, $operations);
if (is_wp_error($result)) {
wp_send_json_error($result->get_error_message());
}
// 创建媒体库附件
$attachment_id = self::create_attachment($result['path'], $filename);
if (is_wp_error($attachment_id)) {
wp_send_json_error($attachment_id->get_error_message());
}
// 返回结果
wp_send_json_success([
'attachment_id' => $attachment_id,
'url' => $result['url'],
'edit_url' => get_edit_post_link($attachment_id),
'size' => size_format($result['size'])
]);
}
public static function save_image_nopriv() {
// 非登录用户处理
if (!get_option('wpiet_allow_guest_upload', false)) {
wp_die('请登录后操作', 403);
}
// 验证reCAPTCHA(如果启用)
if (get_option('wpiet_enable_recaptcha', false)) {
$recaptcha_response = isset($_POST['g-recaptcha-response']) ? $_POST['g-recaptcha-response'] : '';
if (!self::verify_recaptcha($recaptcha_response)) {
wp_send_json_error('验证码验证失败');
}
}
// 继续处理图片
self::save_image();
}
private static function create_attachment($file_path, $filename) {
$file_type = wp_check_filetype($filename, null);
$attachment = [
'post_mime_type' => $file_type['type'],
'post_title' => preg_replace('/.[^.]+$/', '', $filename),
'post_content' => '',
'post_status' => 'inherit',
'guid' => wp_get_upload_dir()['url'] . '/' . $filename
];
$attachment_id = wp_insert_attachment($attachment, $file_path);
if (is_wp_error($attachment_id)) {
return $attachment_id;
}
// 生成附件元数据
require_once(ABSPATH . 'wp-admin/includes/image.php');
$attachment_data = wp_generate_attachment_metadata($attachment_id, $file_path);
wp_update_attachment_metadata($attachment_id, $attachment_data);
return $attachment_id;
}
private static function verify_recaptcha($response) {
$secret_key = get_option('wpiet_recaptcha_secret_key', '');
if (empty($secret_key)) {
return true;
}
$verify_url = 'https://www.google.com/recaptcha/api/siteverify';
$verify_data = [
'secret' => $secret_key,
'response' => $response,
'remoteip' => $_SERVER['REMOTE_ADDR']
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $verify_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($verify_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
$result = curl_exec($ch);
curl_close($ch);
$result_data = json_decode($result, true);
return isset($result_data['success']) && $result_data['success'] === true;
}
}
// includes/class-ajax-handler.php
class WPIET_Ajax_Handler {
public static function init() {
// 保存图片
add_action('wp_ajax_wpiet_save_image', [__CLASS__, 'save_image']);
add_action('wp_ajax_nopriv_wpiet_save_image', [__CLASS__, 'save_image_nopriv']);
// 获取图片信息
add_action('wp_ajax_wpiet_get_image_info', [__CLASS__, 'get_image_info']);
// 批量处理
add_action('wp_ajax_wpiet_batch_process', [__CLASS__, 'batch_process']);
}
public static function save_image() {
// 验证nonce
if (!check_ajax_referer('wpiet_editor_nonce', 'nonce', false)) {
wp_die('安全验证失败', 403);
}
// 验证权限
if (!current_user_can('upload_files')) {
wp_die('权限不足', 403);
}
// 获取数据
$image_data = isset($_POST['image_data']) ? $_POST['image_data'] : '';
$operations = isset($_POST['operations']) ? json_decode(stripslashes($_POST['operations']), true) : [];
$filename = isset($_POST['filename']) ? sanitize_file_name($_POST['filename']) : '';
if (empty($image_data)) {
wp_send_json_error('没有图片数据');
}
// 处理图片
$processor = new WPIET_Image_Processor();
$result = $processor->process_image($image_data, $operations);
if (is_wp_error($result)) {
wp_send_json_error($result->get_error_message());
}
// 创建媒体库附件
$attachment_id = self::create_attachment($result['path'], $filename);
if (is_wp_error($attachment_id)) {
wp_send_json_error($attachment_id->get_error_message());
}
// 返回结果
wp_send_json_success([
'attachment_id' => $attachment_id,
'url' => $result['url'],
'edit_url' => get_edit_post_link($attachment_id),
'size' => size_format($result['size'])
]);
}
public static function save_image_nopriv() {
// 非登录用户处理
if (!get_option('wpiet_allow_guest_upload', false)) {
wp_die('请登录后操作', 403);
}
// 验证reCAPTCHA(如果启用)
if (get_option('wpiet_enable_recaptcha', false)) {
$recaptcha_response = isset($_POST['g-recaptcha-response']) ? $_POST['g-recaptcha-response'] : '';
if (!self::verify_recaptcha($recaptcha_response)) {
wp_send_json_error('验证码验证失败');
}
}
// 继续处理图片
self::save_image();
}
private static function create_attachment($file_path, $filename) {
$file_type = wp_check_filetype($filename, null);
$attachment = [
'post_mime_type' => $file_type['type'],
'post_title' => preg_replace('/.[^.]+$/', '', $filename),
'post_content' => '',
'post_status' => 'inherit',
'guid' => wp_get_upload_dir()['url'] . '/' . $filename
];
$attachment_id = wp_insert_attachment($attachment, $file_path);
if (is_wp_error($attachment_id)) {
return $attachment_id;
}
// 生成附件元数据
require_once(ABSPATH . 'wp-admin/includes/image.php');
$attachment_data = wp_generate_attachment_metadata($attachment_id, $file_path);
wp_update_attachment_metadata($attachment_id, $attachment_data);
return $attachment_id;
}
private static function verify_recaptcha($response) {
$secret_key = get_option('wpiet_recaptcha_secret_key', '');
if (empty($secret_key)) {
return true;
}
$verify_url = 'https://www.google.com/recaptcha/api/siteverify';
$verify_data = [
'secret' => $secret_key,
'response' => $response,
'remoteip' => $_SERVER['REMOTE_ADDR']
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $verify_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($verify_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
$result = curl_exec($ch);
curl_close($ch);
$result_data = json_decode($result, true);
return isset($result_data['success']) && $result_data['success'] === true;
}
}
// public/js/editor-ui.js
class EditorUI {
constructor(editorInstance) {
this.editor = editorInstance;
this.uiState = {
activeTool: null,
isMobile: window.innerWidth < 768,
isFullscreen: false,
showSidebar: true
};
this.initUI();
this.bindEvents();
this.adaptLayout();
}
initUI() {
// 创建工具栏
this.createToolbar();
// 创建侧边栏
this.createSidebar();
// 创建底部控制栏
this.createControls();
// 创建模态框
this.createModals();
}
createToolbar() {
const toolbarHTML = `
<div class="wpiet-toolbar">
<div class="toolbar-section file">
<button class="toolbar-btn" data-action="open">
<i class="icon-folder-open"></i>
<span>打开</span>
</button>
<button class="toolbar-btn" data-action="save">
<i class="icon-save"></i>
<span>保存</span>
</button>
<button class="toolbar-btn" data-action="export">
<i class="icon-download"></i>
<span>导出</span>
</button>
</div>
<div class="toolbar-section edit">
<button class="toolbar-btn" data-tool="crop">
<i class="icon-crop"></i>
<span>裁剪</span>
</button>
<button class="toolbar-btn" data-tool="rotate">
<i class="icon-rotate-right"></i>
<span>旋转</span>
</button>
<button class="toolbar-btn" data-tool="flip">
<i class="icon-flip-horizontal"></i>
<span>翻转</span>
</button>
<button class="toolbar-btn" data-tool="adjust">
<i class="icon-sliders"></i>
<span>调整</span>
</button>
</div>
<div class="toolbar-section effects">
<button class="toolbar-btn" data-tool="filter">
<i class="icon-filter"></i>
<span>滤镜</span>
</button>
<button class="toolbar-btn" data-tool="text">
<i class="icon-type"></i>
<span>文字</span>
</button>
<button class="toolbar-btn" data-tool="sticker">
<i class="icon-sticker"></i>
<span>贴图</span>
</button>
<button class="toolbar-btn" data-tool="frame">
<i class="icon-square"></i>
<span>边框</span>
</button>
</div>
<div class="toolbar-section view">
<button class="toolbar-btn" data-action="zoom-in">
<i class="icon-zoom-in"></i>
</button>
<button class="toolbar-btn" data-action="zoom-out">
<i class="icon-zoom-out"></i>
</button>
<button class="toolbar-btn" data-action="fullscreen">
<i class="icon-maximize"></i>
</button>
<button class="toolbar-btn" data-action="toggle-sidebar">
<i class="icon-sidebar"></i>
</button>
</div>
</div>
`;
document.querySelector('.wpiet-editor-container').insertAdjacentHTML('afterbegin', toolbarHTML);
}
createSidebar() {
const sidebarHTML = `
<div class="wpiet-sidebar ${this.uiState.showSidebar ? 'active' : ''}">
<div class="sidebar-header">
<h3>工具选项</h3>
<button class="sidebar-close">×</button>
</div>
<div class="sidebar-content">
<!-- 动态内容 -->
<div class="tool-options" id="tool-options">
<div class="empty-state">
<i class="icon-tool"></i>
<p>选择一个工具开始编辑</p>
</div>
</div>
<!-- 历史记录 -->
<div class="history-section">
<h4>历史记录</h4>
<div class="history-list" id="history-list"></div>
<div class="history-controls">
<button class="btn-small" id="undo-btn" disabled>
<i class="icon-undo"></i> 撤销
</button>
<button class="btn-small" id="redo-btn" disabled>
<i class="icon-redo"></i> 重做
</button>
</div>
</div>
</div>
</div>
`;
document.querySelector('.wpiet-editor-container').insertAdjacentHTML('beforeend', sidebarHTML);
}
bindEvents() {
// 工具栏按钮点击
document.querySelectorAll('.toolbar-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const action = e.currentTarget.dataset.action;
const tool = e.currentTarget.dataset.tool;
if (action) {
this.handleAction(action);
} else if (tool) {
this.activateTool(tool);
}
});
});
// 窗口大小变化
window.addEventListener('resize', () => {
this.adaptLayout();
});
// 键盘快捷键
document.addEventListener('keydown', (e) => {
this.handleKeyboardShortcuts(e);
});
// 触摸设备支持
if ('ontouchstart' in window) {
this.enableTouchSupport();
}
}
handleAction(action) {
switch(action) {
case 'open':
this.openImagePicker();
break;
case 'save':
this.saveImage();
break;
case 'export':
this.exportImage();
break;
case 'zoom-in':
this.editor.zoomIn();
break;
case 'zoom-out':
this.editor.zoomOut();
break;
case 'fullscreen':
this.toggleFullscreen();
break;
case 'toggle-sidebar':
this.toggleSidebar();
break;
}
}
activateTool(toolName) {
// 更新UI状态
this.uiState.activeTool = toolName;
// 更新工具栏按钮状态
document.querySelectorAll('.toolbar-btn').forEach(btn => {
// public/js/editor-ui.js
class EditorUI {
constructor(editorInstance) {
this.editor = editorInstance;
this.uiState = {
activeTool: null,
isMobile: window.innerWidth < 768,
isFullscreen: false,
showSidebar: true
};
this.initUI();
this.bindEvents();
this.adaptLayout();
}
initUI() {
// 创建工具栏
this.createToolbar();
// 创建侧边栏
this.createSidebar();
// 创建底部控制栏
this.createControls();
// 创建模态框
this.createModals();
}
createToolbar() {
const toolbarHTML = `
<div class="wpiet-toolbar">
<div class="toolbar-section file">
<button class="toolbar-btn" data-action="open">
<i class="icon-folder-open"></i>
<span>打开</span>
</button>
<button class="toolbar-btn" data-action="save">
<i class="icon-save"></i>
<span>保存</span>
</button>
<button class="toolbar-btn" data-action="export">
<i class="icon-download"></i>
<span>导出</span>
</button>
</div>
<div class="toolbar-section edit">
<button class="toolbar-btn" data-tool="crop">
<i class="icon-crop"></i>
<span>裁剪</span>
</button>
<button class="toolbar-btn" data-tool="rotate">
<i class="icon-rotate-right"></i>
<span>旋转</span>
</button>
<button class="toolbar-btn" data-tool="flip">
<i class="icon-flip-horizontal"></i>
<span>翻转</span>
</button>
<button class="toolbar-btn" data-tool="adjust">
<i class="icon-sliders"></i>
<span>调整</span>
</button>
</div>
<div class="toolbar-section effects">
<button class="toolbar-btn" data-tool="filter">
<i class="icon-filter"></i>
<span>滤镜</span>
</button>
<button class="toolbar-btn" data-tool="text">
<i class="icon-type"></i>
<span>文字</span>
</button>
<button class="toolbar-btn" data-tool="sticker">
<i class="icon-sticker"></i>
<span>贴图</span>
</button>
<button class="toolbar-btn" data-tool="frame">
<i class="icon-square"></i>
<span>边框</span>
</button>
</div>
<div class="toolbar-section view">
<button class="toolbar-btn" data-action="zoom-in">
<i class="icon-zoom-in"></i>
</button>
<button class="toolbar-btn" data-action="zoom-out">
<i class="icon-zoom-out"></i>
</button>
<button class="toolbar-btn" data-action="fullscreen">
<i class="icon-maximize"></i>
</button>
<button class="toolbar-btn" data-action="toggle-sidebar">
<i class="icon-sidebar"></i>
</button>
</div>
</div>
`;
document.querySelector('.wpiet-editor-container').insertAdjacentHTML('afterbegin', toolbarHTML);
}
createSidebar() {
const sidebarHTML = `
<div class="wpiet-sidebar ${this.uiState.showSidebar ? 'active' : ''}">
<div class="sidebar-header">
<h3>工具选项</h3>
<button class="sidebar-close">×</button>
</div>
<div class="sidebar-content">
<!-- 动态内容 -->
<div class="tool-options" id="tool-options">
<div class="empty-state">
<i class="icon-tool"></i>
<p>选择一个工具开始编辑</p>
</div>
</div>
<!-- 历史记录 -->
<div class="history-section">
<h4>历史记录</h4>
<div class="history-list" id="history-list"></div>
<div class="history-controls">
<button class="btn-small" id="undo-btn" disabled>
<i class="icon-undo"></i> 撤销
</button>
<button class="btn-small" id="redo-btn" disabled>
<i class="icon-redo"></i> 重做
</button>
</div>
</div>
</div>
</div>
`;
document.querySelector('.wpiet-editor-container').insertAdjacentHTML('beforeend', sidebarHTML);
}
bindEvents() {
// 工具栏按钮点击
document.querySelectorAll('.toolbar-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const action = e.currentTarget.dataset.action;
const tool = e.currentTarget.dataset.tool;
if (action) {
this.handleAction(action);
} else if (tool) {
this.activateTool(tool);
}
});
});
// 窗口大小变化
window.addEventListener('resize', () => {
this.adaptLayout();
});
// 键盘快捷键
document.addEventListener('keydown', (e) => {
this.handleKeyboardShortcuts(e);
});
// 触摸设备支持
if ('ontouchstart' in window) {
this.enableTouchSupport();
}
}
handleAction(action) {
switch(action) {
case 'open':
this.openImagePicker();
break;
case 'save':
this.saveImage();
break;
case 'export':
this.exportImage();
break;
case 'zoom-in':
this.editor.zoomIn();
break;
case 'zoom-out':
this.editor.zoomOut();
break;
case 'fullscreen':
this.toggleFullscreen();
break;
case 'toggle-sidebar':
this.toggleSidebar();
break;
}
}
activateTool(toolName) {
// 更新UI状态
this.uiState.activeTool = toolName;
// 更新工具栏按钮状态
document.querySelectorAll('.toolbar-btn').forEach(btn => {


