1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
Imagick.php
См. документацию.
1<?php
2
9
10namespace Bitrix\Main\File\Image;
11
12use Bitrix\Main\File;
13
19class Imagick extends Engine
20{
22 protected $image;
23 protected $animated = false;
25 protected $jpegSize;
26
30 public function getExifData()
31 {
32 if (function_exists("exif_read_data"))
33 {
34 return parent::getExifData();
35 }
36
37 $result = [];
38
39 if ($this->image !== null)
40 {
41 $exif = $this->image->getImageProperties("exif:*");
42
43 foreach ($exif as $name => $property)
44 {
45 $result[substr($name, 5)] = $property;
46 }
47 }
48
49 return $result;
50 }
51
55 public function load()
56 {
57 $this->clear();
58
59 try
60 {
61 $this->image = new \Imagick();
62
63 $info = $this->getInfo();
64 if (!$info)
65 {
66 return false;
67 }
68
69 if ($this->exceedsMaxSize())
70 {
71 // the image exceeds maximum sizes, optionally substitute it
72 return $this->substituteImage();
73 }
74
75 $needResize = false;
76 if ($info->getFormat() == File\Image::FORMAT_JPEG)
77 {
78 // only for JPEGs
79 $needResize = $this->setJpegOptions();
80 }
81
82 $allowAnimated = (isset($this->options["allowAnimatedImages"]) && $this->options["allowAnimatedImages"] === true);
83
84 // read only the first frame
85 $suffix = ($allowAnimated ? '' : '[0]');
86
87 $this->image->readImage($this->file . $suffix);
88
89 if ($needResize)
90 {
91 // JPEG memory usage optimization
92 $this->image->thumbnailImage($this->jpegSize->getWidth(), $this->jpegSize->getHeight());
93 }
94
95 if ($allowAnimated && $this->image->getNumberImages() > 1)
96 {
97 $this->animated = true;
98 $this->image = $this->image->coalesceImages();
99 }
100
101 return true;
102 }
103 catch (\ImagickException)
104 {
105 return false;
106 }
107 }
108
109 protected function substituteImage()
110 {
111 if (isset($this->options["substImage"]))
112 {
113 // substitute the file with a blank image
114 $substImage = new Imagick($this->options["substImage"]);
115
116 if ($substImage->load())
117 {
118 $this->image = clone $substImage->image;
119 $this->substituted = true;
120
121 return true;
122 }
123 }
124 return false;
125 }
126
127 protected function setJpegOptions()
128 {
129 $this->jpegSize = $this->getJpegSize();
130
131 if ($this->jpegSize)
132 {
133 $source = $this->getInfo()->toRectangle();
134
135 $needResize = $source->resize($this->jpegSize, File\Image::RESIZE_PROPORTIONAL);
136
137 if ($needResize)
138 {
139 // the image is too large to fit into memory
140 $this->image->setOption('jpeg:size', $this->jpegSize->getWidth() . 'x' . $this->jpegSize->getHeight());
141 $this->image->setSize($this->jpegSize->getWidth(), $this->jpegSize->getHeight());
142
143 return true;
144 }
145 }
146 return false;
147 }
148
152 public function rotate($angle, Color $bgColor)
153 {
154 if ($this->image === null)
155 {
156 return false;
157 }
158
159 $color = new \ImagickPixel($bgColor->toRgba());
160
161 foreach ($this->image as $frame)
162 {
163 $frame->rotateImage($color, $angle);
164 }
165
166 return true;
167 }
168
172 public function flipVertical()
173 {
174 if ($this->image === null)
175 {
176 return false;
177 }
178
179 foreach ($this->image as $frame)
180 {
181 $frame->flipImage();
182 }
183
184 return true;
185 }
186
190 public function flipHorizontal()
191 {
192 if ($this->image === null)
193 {
194 return false;
195 }
196
197 foreach ($this->image as $frame)
198 {
199 $frame->flopImage();
200 }
201
202 return true;
203 }
204
208 public function setOrientation($orientation)
209 {
210 if ($this->image === null)
211 {
212 return false;
213 }
214
215 return $this->image->setImageOrientation($orientation);
216 }
217
221 public function resize(Rectangle $source, Rectangle $destination)
222 {
223 if ($this->image === null)
224 {
225 return false;
226 }
227
228 //need crop
229 if ($source->getX() <> 0 || $source->getY() <> 0)
230 {
231 $this->crop($source);
232 }
233
234 //hope Imagick will use the best filter automatically
235 $filter = \Imagick::FILTER_UNDEFINED;
236
237 foreach ($this->image as $frame)
238 {
239 //resizeImage has better quality than scaleImage (scaleImage uses a filter similar to FILTER_BOX)
240 $frame->resizeImage($destination->getWidth(), $destination->getHeight(), $filter, 1);
241 }
242
243 return true;
244 }
245
250 public function crop(Rectangle $source)
251 {
252 if ($this->image === null)
253 {
254 return false;
255 }
256
257 $this->image->setImagePage(0, 0, 0, 0);
258
259 foreach ($this->image as $frame)
260 {
261 $frame->cropImage($source->getWidth(), $source->getHeight(), $source->getX(), $source->getY());
262 }
263
264 return true;
265 }
266
270 public function blur(int $sigma): bool
271 {
272 if ($this->image === null)
273 {
274 return false;
275 }
276
277 $sigma = max(1, min(100, round($sigma)));
278 $this->image->blurImage(0, $sigma);
279
280 return true;
281 }
282
286 public function filter(Mask $mask)
287 {
288 if ($this->image === null)
289 {
290 return false;
291 }
292
293 $imVersion = \Imagick::getVersion();
294 if ($imVersion['versionNumber'] < 0x700 || phpversion('imagick') < '3.4.0')
295 {
296 $this->image->convolveImage($mask->getVector());
297 }
298 else
299 {
300 $kernel = \ImagickKernel::fromMatrix($mask->getValue());
301 $this->image->convolveImage($kernel);
302 }
303
304 return true;
305 }
306
310 public function drawTextWatermark(TextWatermark $watermark)
311 {
312 if ($this->image === null)
313 {
314 return false;
315 }
316
317 $font = $watermark->getFont();
318
319 if (!file_exists($font))
320 {
321 return false;
322 }
323
324 $utfText = $watermark->getUtfText();
325
326 $width = $this->getWidth();
327 $height = $this->getHeight();
328
329 $draw = new \ImagickDraw();
330 $draw->setFont($font);
331 $draw->setFillColor(new \ImagickPixel($watermark->getColor()->toRgba()));
332
333 if (($textWidth = $watermark->getWidth()) > 0)
334 {
335 $draw->setFontSize(20);
336
337 $metrics = $this->image->queryFontMetrics($draw, $utfText);
338
339 $scale = 1.0;
340 if ($metrics["textWidth"] > 0)
341 {
342 $scale = $textWidth / $metrics["textWidth"];
343 }
344
345 $fontSize = 20 * $scale;
346 $draw->setFontSize($fontSize);
347
348 $position = new Rectangle($textWidth, $metrics["textHeight"] * $scale);
349 }
350 else
351 {
352 //in GD resolution is 96 dpi, we should increase size
353 $fontSize = $watermark->getFontSize($width) * (96 / 72);
354 $draw->setFontSize($fontSize);
355
356 $metrics = $this->image->queryFontMetrics($draw, $utfText);
357
358 $position = new Rectangle($metrics["textWidth"], $metrics["textHeight"]);
359 }
360
361 $watermark->alignPosition($width, $height, $position);
362
363 $fontSize *= (72 / 90); //back to pixels
364
366 {
367 //Try to take into consideration font's descenders.
368 //Coordinates in annotateImage are for font's *baseline*.
369 //Let the descenders be 20% of the font size.
370 $descender = $fontSize * 0.2;
371 $y = $position->getY() + $position->getHeight() - $descender; //baseline
372 }
373 else
374 {
375 $y = $position->getY() + $fontSize;
376 }
377
378 return $this->image->annotateImage($draw, $position->getX(), $y, 0, $utfText);
379 }
380
384 public function drawImageWatermark(ImageWatermark $watermark)
385 {
386 if ($this->image === null)
387 {
388 return false;
389 }
390
391 if (($image = $this->loadWatermark($watermark)) === null)
392 {
393 return false;
394 }
395
396 $watermarkWidth = $image->getWidth();
397 $watermarkHeight = $image->getHeight();
398
399 $position = new Rectangle($watermarkWidth, $watermarkHeight);
400
401 $width = $this->getWidth();
402 $height = $this->getHeight();
403
404 $watermark->alignPosition($width, $height, $position);
405
406 $watermarkAlpha = $watermark->getAlpha();
407
408 if (intval(round($watermarkAlpha, 2)) < 1) //1% precision
409 {
410 //apply alpha to the watermark
411 $image->image->evaluateImage(\Imagick::EVALUATE_MULTIPLY, $watermarkAlpha, \Imagick::CHANNEL_ALPHA);
412 }
413
414 $repeat = ($watermark->getMode() == ImageWatermark::MODE_REPEAT);
415
416 $posY = $position->getY();
417 while (true)
418 {
419 $posX = $position->getX();
420 while (true)
421 {
422 $this->image->compositeImage($image->image, \Imagick::COMPOSITE_OVER, $posX, $posY);
423
424 $posX += $watermarkWidth;
425 if (!$repeat || $posX > $width)
426 {
427 break;
428 }
429 }
430
431 $posY += $watermarkHeight;
432 if (!$repeat || $posY > $height)
433 {
434 break;
435 }
436 }
437
438 $image->clear();
439
440 return true;
441 }
442
446 public function save($file, $quality = 95, $format = null)
447 {
448 if ($this->image === null)
449 {
450 return false;
451 }
452
453 $prefix = "";
454 if ($format === null)
455 {
456 $format = $this->getInfo()?->getFormat();
457 }
458 if ($format !== null)
459 {
460 $format = static::convertFormat($format);
461 if ($format !== null)
462 {
463 $prefix = "{$format}:";
464 }
465 }
466
467 $this->image->setImageCompressionQuality($quality);
468
469 if ($format === "gif" || ($format = $this->image->getImageFormat()) === "GIF" || $format === "GIF87")
470 {
471 //strange artefacts with transparency - we limit the palette to 255 colors to fix it
472 $this->image->quantizeImage(255, \Imagick::COLORSPACE_SRGB, 0, false, false);
473 }
474
475 if ($this->animated)
476 {
477 return $this->image->deconstructImages()->writeImages($prefix . $file, true);
478 }
479 else
480 {
481 return $this->image->writeImage($prefix . $file);
482 }
483 }
484
488 public function getWidth()
489 {
490 return $this->image->getImageWidth();
491 }
492
496 public function getHeight()
497 {
498 return $this->image->getImageHeight();
499 }
500
504 public function clear()
505 {
506 if ($this->image !== null)
507 {
508 $this->image->clear();
509 $this->image = null;
510 $this->animated = false;
511 $this->jpegSize = null;
512 }
513 }
514
515 protected static function convertFormat($format)
516 {
517 static $formats = [
518 File\Image::FORMAT_BMP => "bmp",
519 File\Image::FORMAT_GIF => "gif",
520 File\Image::FORMAT_JPEG => "jpg",
521 File\Image::FORMAT_PNG => "png",
522 File\Image::FORMAT_WEBP => "webp",
523 ];
524
525 if (isset($formats[$format]))
526 {
527 return $formats[$format];
528 }
529 return null;
530 }
531
532 protected function getJpegSize()
533 {
534 if (isset($this->options["jpegLoadSize"]) && is_array($this->options["jpegLoadSize"]))
535 {
536 return new Rectangle($this->options["jpegLoadSize"][0], $this->options["jpegLoadSize"][1]);
537 }
538
539 return null;
540 }
541}
exceedsMaxSize()
Определения Engine.php:323
getInfo($flashEnabled=false)
Определения Engine.php:57
loadWatermark(ImageWatermark $watermark)
Определения Engine.php:231
blur(int $sigma)
Определения Imagick.php:270
rotate($angle, Color $bgColor)
Определения Imagick.php:152
drawTextWatermark(TextWatermark $watermark)
Определения Imagick.php:310
save($file, $quality=95, $format=null)
Определения Imagick.php:446
static convertFormat($format)
Определения Imagick.php:515
crop(Rectangle $source)
Определения Imagick.php:250
drawImageWatermark(ImageWatermark $watermark)
Определения Imagick.php:384
filter(Mask $mask)
Определения Imagick.php:286
setOrientation($orientation)
Определения Imagick.php:208
resize(Rectangle $source, Rectangle $destination)
Определения Imagick.php:221
getVector()
Определения Mask.php:42
getValue()
Определения Mask.php:34
const ALIGN_BOTTOM
Определения Watermark.php:18
alignPosition($width, $height, Rectangle $position)
Определения Watermark.php:61
$result
Определения get_property_values.php:14
$filter
Определения iblock_catalog_list.php:54
$name
Определения menu_edit.php:35
Определения action.php:3
Определения Image.php:9
$textWidth
Определения template_pdf.php:80
$width
Определения html.php:68
$fontSize
Определения pdf.php:30