Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/File/Image/ |
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/File/Image/Imagick.php |
<?php /** * Bitrix Framework * @package bitrix * @subpackage main * @copyright 2001-2023 Bitrix */ namespace Bitrix\Main\File\Image; use Bitrix\Main\File; /** * Class Imagick * @see https://imagemagick.org/script/defines.php * @see https://imagemagick.org/script/architecture.php */ class Imagick extends Engine { /** @var \Imagick */ protected $image; protected $animated = false; /** @var Rectangle */ protected $jpegSize; /** * @inheritDoc */ public function getExifData() { if(function_exists("exif_read_data")) { return parent::getExifData(); } $result = []; if($this->image !== null) { $exif = $this->image->getImageProperties("exif:*"); foreach($exif as $name => $property) { $result[substr($name, 5)] = $property; } } return $result; } /** * @inheritDoc */ public function load() { $this->clear(); try { $this->image = new \Imagick(); $info = $this->getInfo(); if (!$info) { return false; } if ($this->exceedsMaxSize()) { // the image exceeds maximum sizes, optionally substitute it return $this->substituteImage(); } $needResize = false; if ($info->getFormat() == File\Image::FORMAT_JPEG) { // only for JPEGs $needResize = $this->setJpegOptions(); } $allowAnimated = (isset($this->options["allowAnimatedImages"]) && $this->options["allowAnimatedImages"] === true); // read only the first frame $suffix = ($allowAnimated ? '' : '[0]'); $this->image->readImage($this->file . $suffix); if ($needResize) { // JPEG memory usage optimization $this->image->thumbnailImage($this->jpegSize->getWidth(), $this->jpegSize->getHeight()); } if ($allowAnimated && $this->image->getNumberImages() > 1) { $this->animated = true; $this->image = $this->image->coalesceImages(); } return true; } catch (\ImagickException $e) { return false; } } protected function substituteImage() { if (isset($this->options["substImage"])) { // substitute the file with a blank image $substImage = new Imagick($this->options["substImage"]); if ($substImage->load()) { $this->image = clone $substImage->image; $this->substituted = true; return true; } } return false; } protected function setJpegOptions() { $this->jpegSize = $this->getJpegSize(); if ($this->jpegSize) { $source = $this->getInfo()->toRectangle(); $needResize = $source->resize($this->jpegSize, File\Image::RESIZE_PROPORTIONAL); if ($needResize) { // the image is too large to fit into memory $this->image->setOption('jpeg:size', $this->jpegSize->getWidth() . 'x' . $this->jpegSize->getHeight()); $this->image->setSize($this->jpegSize->getWidth(), $this->jpegSize->getHeight()); return true; } } return false; } /** * @inheritDoc */ public function rotate($angle, Color $bgColor) { if ($this->image === null) { return false; } $color = new \ImagickPixel($bgColor->toRgba()); foreach ($this->image as $frame) { $frame->rotateImage($color, $angle); } return true; } /** * @inheritDoc */ public function flipVertical() { if ($this->image === null) { return false; } foreach ($this->image as $frame) { $frame->flipImage(); } return true; } /** * @inheritDoc */ public function flipHorizontal() { if ($this->image === null) { return false; } foreach ($this->image as $frame) { $frame->flopImage(); } return true; } /** * @inheritDoc */ public function setOrientation($orientation) { if($this->image === null) { return false; } return $this->image->setImageOrientation($orientation); } /** * @inheritDoc */ public function resize(Rectangle $source, Rectangle $destination) { if($this->image === null) { return false; } //need crop if($source->getX() <> 0 || $source->getY() <> 0) { $this->crop($source); } //hope Imagick will use the best filter automatically $filter = \Imagick::FILTER_UNDEFINED; foreach ($this->image as $frame) { //resizeImage has better quality than scaleImage (scaleImage uses a filter similar to FILTER_BOX) $frame->resizeImage($destination->getWidth(), $destination->getHeight(), $filter, 1); } return true; } /** * @param Rectangle $source * @return bool */ public function crop(Rectangle $source) { if($this->image === null) { return false; } $this->image->setImagePage(0, 0, 0, 0); foreach ($this->image as $frame) { $frame->cropImage($source->getWidth(), $source->getHeight(), $source->getX(), $source->getY()); } return true; } /** * @inheritDoc */ public function filter(Mask $mask) { if($this->image === null) { return false; } $imVersion = \Imagick::getVersion(); if ($imVersion['versionNumber'] < 0x700 || phpversion('imagick') < '3.4.0') { $this->image->convolveImage($mask->getVector()); } else { $kernel = \ImagickKernel::fromMatrix($mask->getValue()); $this->image->convolveImage($kernel); } return true; } /** * @inheritDoc */ public function drawTextWatermark(TextWatermark $watermark) { if($this->image === null) { return false; } $font = $watermark->getFont(); if(!file_exists($font)) { return false; } $utfText = $watermark->getUtfText(); $width = $this->getWidth(); $height = $this->getHeight(); $draw = new \ImagickDraw(); $draw->setFont($font); $draw->setFillColor(new \ImagickPixel($watermark->getColor()->toRgba())); if(($textWidth = $watermark->getWidth()) > 0) { $draw->setFontSize(20); $metrics = $this->image->queryFontMetrics($draw, $utfText); $scale = 1.0; if($metrics["textWidth"] > 0) { $scale = $textWidth / $metrics["textWidth"]; } $fontSize = 20 * $scale; $draw->setFontSize($fontSize); $position = new Rectangle($textWidth, $metrics["textHeight"] * $scale); } else { //in GD resolution is 96 dpi, we should increase size $fontSize = $watermark->getFontSize($width) * (96/72); $draw->setFontSize($fontSize); $metrics = $this->image->queryFontMetrics($draw, $utfText); $position = new Rectangle($metrics["textWidth"], $metrics["textHeight"]); } $watermark->alignPosition($width, $height, $position); $fontSize *= (72/90); //back to pixels if($watermark->getVerticalAlignment() == Watermark::ALIGN_BOTTOM) { //Try to take into consideration font's descenders. //Coordinates in annotateImage are for font's *baseline*. //Let the descenders be 20% of the font size. $descender = $fontSize * 0.2; $y = $position->getY() + $position->getHeight() - $descender; //baseline } else { $y = $position->getY() + $fontSize; } return $this->image->annotateImage($draw, $position->getX(), $y, 0, $utfText); } /** * @inheritDoc */ public function drawImageWatermark(ImageWatermark $watermark) { if($this->image === null) { return false; } if(($image = $this->loadWatermark($watermark)) === null) { return false; } $watermarkWidth = $image->getWidth(); $watermarkHeight = $image->getHeight(); $position = new Rectangle($watermarkWidth, $watermarkHeight); $width = $this->getWidth(); $height = $this->getHeight(); $watermark->alignPosition($width, $height, $position); $watermarkAlpha = $watermark->getAlpha(); if(intval(round($watermarkAlpha, 2)) < 1) //1% precision { //apply alpha to the watermark $image->image->evaluateImage(\Imagick::EVALUATE_MULTIPLY, $watermarkAlpha, \Imagick::CHANNEL_ALPHA); } $repeat = ($watermark->getMode() == ImageWatermark::MODE_REPEAT); $posY = $position->getY(); while(true) { $posX = $position->getX(); while(true) { $this->image->compositeImage($image->image, \Imagick::COMPOSITE_OVER, $posX, $posY); $posX += $watermarkWidth; if($repeat == false || $posX > $width) { break; } } $posY += $watermarkHeight; if($repeat == false || $posY > $height) { break; } } $image->clear(); return true; } /** * @inheritDoc */ public function save($file, $quality = 95, $format = null) { if ($this->image === null) { return false; } $prefix = ""; if ($format !== null) { $format = static::convertFormat($format); if($format !== null) { $prefix = "{$format}:"; } } $this->image->setImageCompressionQuality($quality); if ($format === "gif" || ($format = $this->image->getImageFormat()) === "GIF" || $format === "GIF87") { //strange artefacts with transparency - we limit the palette to 255 colors to fix it $this->image->quantizeImage(255, \Imagick::COLORSPACE_SRGB, 0, false, false); } if ($this->animated) { return $this->image->deconstructImages()->writeImages($prefix.$file, true); } else { return $this->image->writeImage($prefix.$file); } } /** * @inheritDoc */ public function getWidth() { return $this->image->getImageWidth(); } /** * @inheritDoc */ public function getHeight() { return $this->image->getImageHeight(); } /** * @inheritDoc */ public function clear() { if($this->image !== null) { $this->image->clear(); $this->image = null; $this->animated = false; $this->jpegSize = null; } } protected static function convertFormat($format) { static $formats = [ File\Image::FORMAT_BMP => "bmp", File\Image::FORMAT_GIF => "gif", File\Image::FORMAT_JPEG => "jpg", File\Image::FORMAT_PNG => "png", File\Image::FORMAT_WEBP => "webp", ]; if(isset($formats[$format])) { return $formats[$format]; } return null; } protected function getJpegSize() { if (isset($this->options["jpegLoadSize"]) && is_array($this->options["jpegLoadSize"])) { return new Rectangle($this->options["jpegLoadSize"][0], $this->options["jpegLoadSize"][1]); } return null; } }