PluginProbe
WPWaterMark 轻水印插件 / 5.1.4
WPWaterMark 轻水印插件 v5.1.4
5.2.4 5.2.2 5.2.1 5.1.7 5.1.6 trunk 4.3 5.0.0 5.0.1 5.1.2 5.1.3 5.1.4 5.1.5
wpwatermark / WaterMarkHandler.php

WaterMarkHandler.php in WPWaterMark 轻水印插件 5.1.4, at WaterMarkHandler.php

530 lines 19.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WaterMark Handler Class
4 *
5 * Handles all watermark related operations with improved organization and caching
6 */
7 class WaterMarkHandler {
8 /** @var string */
9 private $cache_dir;
10
11 /** @var string */
12 private $font_dir;
13
14 /** @var array */
15 private $options;
16
17 /**
18 * Constructor
19 *
20 * @param array $options Watermark options
21 */
22 public function __construct(array $options) {
23 $this->options = $options;
24 $this->cache_dir = plugin_dir_path(__FILE__) . 'cache/';
25 $this->font_dir = plugin_dir_path(__FILE__) . 'fonts/';
26
27 // Ensure cache directory exists
28 if (!file_exists($this->cache_dir)) {
29 wp_mkdir_p($this->cache_dir);
30 }
31 }
32
33 /**
34 * Generate cache key for watermark options
35 *
36 * @param string $img_url
37 * @param array $options
38 * @return string
39 */
40 private function generateCacheKey(string $img_url, array $options): string {
41 return md5($img_url . serialize($options));
42 }
43
44 /**
45 * Check if cached version exists
46 *
47 * @param string $cache_key
48 * @return string|false
49 */
50 private function getCachedImage(string $cache_key) {
51 $cache_file = $this->cache_dir . $cache_key . '.jpg';
52 if (file_exists($cache_file) && (time() - filemtime($cache_file) < 3600)) {
53 return $cache_file;
54 }
55 return false;
56 }
57
58 /**
59 * Create text watermark with improved error handling and caching
60 *
61 * @param string $img_url
62 * @param string $new_img_url
63 * @param string $text
64 * @param array $options
65 * @return bool
66 */
67 public function createTextWatermark(string $img_url, string $new_img_url, string $text, array $options = []): bool {
68 try {
69 // Generate cache key
70 $cache_key = $this->generateCacheKey($img_url, array_merge($this->options, $options));
71
72 // Check cache
73 $cached_file = $this->getCachedImage($cache_key);
74 if ($cached_file) {
75 copy($cached_file, $new_img_url);
76 return true;
77 }
78
79 // Validate image
80 $img_size = getimagesize($img_url);
81 if (empty($img_size)) {
82 throw new Exception('Invalid image file');
83 }
84
85 // Validate dimensions
86 if ($img_size[0] < $this->options['watermark_min_width'] ||
87 $img_size[1] < $this->options['watermark_min_height']) {
88 throw new Exception('Image dimensions too small for watermark');
89 }
90
91 // Create image resource
92 $im = $this->createImageResource($img_url, $img_size['mime']);
93 if (!$im) {
94 throw new Exception('Failed to create image resource');
95 }
96
97 // Apply text watermark
98 $text_color = $this->parseColor($options['text_color'] ?? $this->options['text_color']);
99 $font_file = $this->font_dir . ($options['text_font'] ?? $this->options['text_font']);
100
101 if (!file_exists($font_file)) {
102 throw new Exception('Font file not found');
103 }
104
105 $text_color = imagecolorallocate($im, $text_color['r'], $text_color['g'], $text_color['b']);
106 $position = $this->calculatePosition($options['position'] ?? $this->options['watermark_position'],
107 $img_size[0],
108 $img_size[1],
109 $text);
110
111 // Add watermark
112 imagettftext(
113 $im,
114 $options['text_size'] ?? $this->options['text_size'],
115 $options['text_angle'] ?? $this->options['text_angle'],
116 $position['x'],
117 $position['y'],
118 $text_color,
119 $font_file,
120 $text
121 );
122
123 // Save image
124 $this->saveImage($im, $new_img_url, $img_size['mime']);
125
126 // Cache result
127 copy($new_img_url, $this->cache_dir . $cache_key . '.jpg');
128
129 imagedestroy($im);
130 return true;
131
132 } catch (Exception $e) {
133 error_log('WaterMark Error: ' . $e->getMessage());
134 return false;
135 }
136 }
137
138 /**
139 * Create image watermark with improved error handling and caching
140 *
141 * @param string $img_url
142 * @param string $watermark_url
143 * @param string $new_img_url
144 * @param array $options
145 * @return bool
146 */
147 public function createImageWatermark($img_url, $watermark_url, $new_img_url, $options = []) {
148 try {
149 // Generate cache key
150 $cache_key = $this->generateCacheKey($img_url, array_merge($this->options, $options));
151
152 // Check cache
153 $cached_file = $this->getCachedImage($cache_key);
154 if ($cached_file) {
155 copy($cached_file, $new_img_url);
156 return true;
157 }
158
159 // Validate image
160 $img_size = getimagesize($img_url);
161 if (empty($img_size)) {
162 throw new Exception('Invalid image file');
163 }
164
165 // Validate dimensions
166 if ($img_size[0] < $this->options['watermark_min_width'] ||
167 $img_size[1] < $this->options['watermark_min_height']) {
168 throw new Exception('Image dimensions too small for watermark');
169 }
170
171 // Create image resource
172 $im = $this->createImageResource($img_url, $img_size['mime']);
173 if (!$im) {
174 throw new Exception('Failed to create image resource');
175 }
176
177 // Load watermark image
178 $watermark_size = getimagesize($watermark_url);
179 if (!$watermark_size) {
180 throw new Exception('Invalid watermark image');
181 }
182
183 $watermark = $this->createImageResource($watermark_url, $watermark_size['mime']);
184 if (!$watermark) {
185 throw new Exception('Failed to create watermark resource');
186 }
187
188 // Calculate position
189 $position = $this->calculatePosition(
190 $options['watermark_position'] ?? $this->options['watermark_position'],
191 $img_size[0],
192 $img_size[1],
193 '',
194 $watermark_size[0],
195 $watermark_size[1]
196 );
197
198 // 创建临时图像用于处理
199 $temp = imagecreatetruecolor($img_size[0], $img_size[1]);
200
201 // 设置临时图像的透明度支持
202 imagealphablending($temp, false);
203 imagesavealpha($temp, true);
204
205 // 如果原图是PNG,设置透明背景
206 if ($img_size['mime'] === 'image/png') {
207 $transparent = imagecolorallocatealpha($temp, 0, 0, 0, 127);
208 imagefilledrectangle($temp, 0, 0, $img_size[0], $img_size[1], $transparent);
209 }
210
211 // 复制原图到临时图像
212 imagecopy($temp, $im, 0, 0, 0, 0, $img_size[0], $img_size[1]);
213
214 // 获取透明度设置
215 $opacity = ($options['watermark_diaphaneity'] ?? $this->options['watermark_diaphaneity']);
216
217 // 如果是PNG水印,保持�
218 �原有透明度
219 if ($watermark_size['mime'] === 'image/png') {
220 // 创建水印临时图像
221 $watermark_temp = imagecreatetruecolor($watermark_size[0], $watermark_size[1]);
222 imagealphablending($watermark_temp, false);
223 imagesavealpha($watermark_temp, true);
224
225 // 设置完�
226 �透明背景
227 $transparent = imagecolorallocatealpha($watermark_temp, 0, 0, 0, 127);
228 imagefilledrectangle($watermark_temp, 0, 0, $watermark_size[0], $watermark_size[1], $transparent);
229
230 // 复制水印到临时图像
231 imagecopy($watermark_temp, $watermark, 0, 0, 0, 0, $watermark_size[0], $watermark_size[1]);
232
233 // 应用用户设置的透明度
234 if ($opacity < 100) {
235 // 逐像素调整透明度
236 for ($x = 0; $x < $watermark_size[0]; $x++) {
237 for ($y = 0; $y < $watermark_size[1]; $y++) {
238 $color = imagecolorsforindex($watermark_temp, imagecolorat($watermark_temp, $x, $y));
239 $alpha = 127 - ((127 - $color['alpha']) * $opacity / 100);
240 $new_color = imagecolorallocatealpha(
241 $watermark_temp,
242 $color['red'],
243 $color['green'],
244 $color['blue'],
245 intval($alpha)
246 );
247 imagesetpixel($watermark_temp, $x, $y, $new_color);
248 }
249 }
250 }
251
252 // 合并水印到目标图像
253 imagealphablending($temp, true);
254 imagecopy($temp, $watermark_temp, $position['x'], $position['y'], 0, 0, $watermark_size[0], $watermark_size[1]);
255 imagedestroy($watermark_temp);
256 } else {
257 // 非PNG水印的处理
258 imagealphablending($temp, true);
259 $this->imagecopymerge_alpha(
260 $temp, $watermark,
261 $position['x'], $position['y'],
262 0, 0,
263 $watermark_size[0], $watermark_size[1],
264 $opacity
265 );
266 }
267
268 // 保存最终图像
269 if ($img_size['mime'] === 'image/png') {
270 imagealphablending($temp, false);
271 imagesavealpha($temp, true);
272 }
273 $this->saveImage($temp, $new_img_url, $img_size['mime']);
274
275 // 设置缓存文件扩展名
276 $cache_file = $this->cache_dir . $cache_key .
277 ($img_size['mime'] === 'image/png' ? '.png' : '.jpg');
278
279 // 保存缓存
280 copy($new_img_url, $cache_file);
281
282 // �
283 理资源
284 imagedestroy($temp);
285 imagedestroy($im);
286 imagedestroy($watermark);
287
288 return true;
289
290 } catch (Exception $e) {
291 error_log('WaterMark Error: ' . $e->getMessage());
292 return false;
293 }
294 }
295
296 /**
297 * Helper function to create image resource
298 *
299 * @param string $img_url
300 * @param string $mime_type
301 * @return resource|false
302 */
303 private function createImageResource(string $img_url, string $mime_type) {
304 $create_functions = [
305 'image/jpeg' => 'imagecreatefromjpeg',
306 'image/png' => 'imagecreatefrompng',
307 'image/gif' => 'imagecreatefromgif'
308 ];
309
310 if (isset($create_functions[$mime_type])) {
311 $im = call_user_func($create_functions[$mime_type], $img_url);
312
313 // 特别处理PNG图片的透明度
314 if ($mime_type === 'image/png') {
315 imagealphablending($im, false);
316 imagesavealpha($im, true);
317 }
318
319 return $im;
320 }
321
322 return false;
323 }
324
325 /**
326 * Helper function to save image
327 *
328 * @param resource $im
329 * @param string $filename
330 * @param string $mime_type
331 * @return bool
332 */
333 private function saveImage($im, string $filename, string $mime_type): bool {
334 switch ($mime_type) {
335 case 'image/jpeg':
336 return imagejpeg($im, $filename, 95); // 95% quality for JPEG
337 case 'image/png':
338 return imagepng($im, $filename, 0); // 0-9, 0 for no compression to maintain quality
339 case 'image/gif':
340 return imagegif($im, $filename);
341 default:
342 return false;
343 }
344 }
345
346 /**
347 * Parse hex color to RGB
348 */
349 private function parseColor($hex_color) {
350 $hex_color = ltrim($hex_color, '#');
351 return [
352 'r' => hexdec(substr($hex_color, 0, 2)),
353 'g' => hexdec(substr($hex_color, 2, 2)),
354 'b' => hexdec(substr($hex_color, 4, 2))
355 ];
356 }
357
358 /**
359 * Helper function for alpha-enabled imagecopymerge
360 *
361 * @param resource $dst_im
362 * @param resource $src_im
363 * @param int $dst_x
364 * @param int $dst_y
365 * @param int $src_x
366 * @param int $src_y
367 * @param int $src_w
368 * @param int $src_h
369 * @param int $pct
370 * @return void
371 */
372 private function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct) {
373 // 确保透明度在有效范围�
374
375 $pct = min(100, max(0, $pct));
376
377 // 创建临时图像
378 $cut = imagecreatetruecolor($src_w, $src_h);
379
380 // 设置完�
381 �透明背景
382 imagealphablending($cut, false);
383 imagesavealpha($cut, true);
384 $transparent = imagecolorallocatealpha($cut, 0, 0, 0, 127);
385 imagefilledrectangle($cut, 0, 0, $src_w, $src_h, $transparent);
386
387 // 复制目标区域到临时图像
388 imagecopy($cut, $dst_im, 0, 0, intval($dst_x), intval($dst_y), $src_w, $src_h);
389
390 // 启用混合模式
391 imagealphablending($cut, true);
392
393 // 应用水印到临时图像
394 $this->imagecopymerge_alpha_pixel($cut, $src_im, 0, 0, intval($src_x), intval($src_y), $src_w, $src_h, $pct);
395
396 // 保持目标图像的透明度
397 imagealphablending($dst_im, true);
398 imagesavealpha($dst_im, true);
399
400 // 将处理后的临时图像复制回目标图像
401 imagecopy($dst_im, $cut, intval($dst_x), intval($dst_y), 0, 0, $src_w, $src_h);
402
403 // �
404
405 imagedestroy($cut);
406 }
407
408 /**
409 * 逐像素处理透明度
410 */
411 private function imagecopymerge_alpha_pixel($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct) {
412 if ($pct == 0) return;
413
414 // 逐像素处理
415 for ($y = 0; $y < $src_h; ++$y) {
416 for ($x = 0; $x < $src_w; ++$x) {
417 $src_color = imagecolorsforindex($src_im, imagecolorat($src_im, $src_x + $x, $src_y + $y));
418 $dst_color = imagecolorsforindex($dst_im, imagecolorat($dst_im, $dst_x + $x, $dst_y + $y));
419
420 // 计算新的透明度
421 $src_alpha = 127 - ($src_color['alpha'] * $pct / 100);
422 $dst_alpha = 127 - $dst_color['alpha'];
423 $final_alpha = 127 - (($src_alpha + $dst_alpha) / 2);
424
425 // 混合颜色
426 $final_red = ($src_color['red'] * $pct + $dst_color['red'] * (100 - $pct)) / 100;
427 $final_green = ($src_color['green'] * $pct + $dst_color['green'] * (100 - $pct)) / 100;
428 $final_blue = ($src_color['blue'] * $pct + $dst_color['blue'] * (100 - $pct)) / 100;
429
430 // 创建新颜色
431 $final_color = imagecolorallocatealpha(
432 $dst_im,
433 intval($final_red),
434 intval($final_green),
435 intval($final_blue),
436 intval($final_alpha)
437 );
438
439 // 设置像素
440 imagesetpixel($dst_im, $dst_x + $x, $dst_y + $y, $final_color);
441 }
442 }
443 }
444
445 /**
446 * Calculate watermark position
447 *
448 * @param string $position
449 * @param int $img_width
450 * @param int $img_height
451 * @param string $text
452 * @param int $mark_width
453 * @param int $mark_height
454 * @return array{x: int, y: int}
455 */
456 private function calculatePosition($position, $img_width, $img_height, $text = '', $mark_width = 0, $mark_height = 0) {
457 $margin = intval($this->options['watermark_margin']);
458
459 // For text watermark
460 if ($text !== '') {
461 $font_size = $this->options['text_size'];
462 $font_file = $this->font_dir . $this->options['text_font'];
463 $text_box = imagettfbbox($font_size, 0, $font_file, $text);
464 $mark_width = abs($text_box[4] - $text_box[0]);
465 $mark_height = abs($text_box[1] - $text_box[5]);
466 }
467
468 // Calculate grid dimensions
469 $grid_width = $img_width / 3;
470 $grid_height = $img_height / 3;
471
472 // Calculate position based on grid
473 switch ($position) {
474 case 'top-left':
475 return [
476 'x' => $margin,
477 'y' => $margin
478 ];
479
480 case 'top-center':
481 return [
482 'x' => intval($grid_width + ($grid_width - $mark_width) / 2),
483 'y' => $margin
484 ];
485
486 case 'top-right':
487 return [
488 'x' => intval($img_width - $mark_width - $margin),
489 'y' => $margin
490 ];
491
492 case 'middle-left':
493 return [
494 'x' => $margin,
495 'y' => intval($grid_height + ($grid_height - $mark_height) / 2)
496 ];
497
498 case 'middle-center':
499 return [
500 'x' => intval($grid_width + ($grid_width - $mark_width) / 2),
501 'y' => intval($grid_height + ($grid_height - $mark_height) / 2)
502 ];
503
504 case 'middle-right':
505 return [
506 'x' => intval($img_width - $mark_width - $margin),
507 'y' => intval($grid_height + ($grid_height - $mark_height) / 2)
508 ];
509
510 case 'bottom-left':
511 return [
512 'x' => $margin,
513 'y' => intval($img_height - $mark_height - $margin)
514 ];
515
516 case 'bottom-center':
517 return [
518 'x' => intval($grid_width + ($grid_width - $mark_width) / 2),
519 'y' => intval($img_height - $mark_height - $margin)
520 ];
521
522 case 'bottom-right':
523 default:
524 return [
525 'x' => intval($img_width - $mark_width - $margin),
526 'y' => intval($img_height - $mark_height - $margin)
527 ];
528 }
529 }
530 }