1<?php 2 3declare(strict_types=1); 4 5/* 6 * This file is part of the TYPO3 CMS project. 7 * 8 * It is free software; you can redistribute it and/or modify it under 9 * the terms of the GNU General Public License, either version 2 10 * of the License, or any later version. 11 * 12 * For the full copyright and license information, please read the 13 * LICENSE.txt file that was distributed with this source code. 14 * 15 * The TYPO3 project - inspiring people to share! 16 */ 17 18namespace TYPO3\CMS\Core\Imaging; 19 20use TYPO3\CMS\Core\Imaging\ImageManipulation\Area; 21use TYPO3\CMS\Core\Resource\ProcessedFile; 22use TYPO3\CMS\Core\Resource\Processing\LocalPreviewHelper; 23use TYPO3\CMS\Core\Resource\Processing\TaskInterface; 24use TYPO3\CMS\Core\Utility\GeneralUtility; 25 26/** 27 * Representing an image dimension (width and height) 28 * and calculating the dimension from a source with a given processing instruction 29 */ 30class ImageDimension 31{ 32 /** 33 * @var int 34 */ 35 private $width; 36 37 /** 38 * @var int 39 */ 40 private $height; 41 42 public function __construct(int $width, int $height) 43 { 44 $this->width = $width; 45 $this->height = $height; 46 } 47 48 public function getWidth(): int 49 { 50 return $this->width; 51 } 52 53 public function getHeight(): int 54 { 55 return $this->height; 56 } 57 58 public static function fromProcessingTask(TaskInterface $task): self 59 { 60 $config = self::getConfigurationForImageCropScaleMask($task); 61 $processedFile = $task->getTargetFile(); 62 $isCropped = false; 63 if (($config['crop'] ?? null) instanceof Area) { 64 $isCropped = true; 65 $imageDimension = new self( 66 (int)round($config['crop']->getWidth()), 67 (int)round($config['crop']->getHeight()) 68 ); 69 } else { 70 $imageDimension = new self( 71 (int)$processedFile->getOriginalFile()->getProperty('width'), 72 (int)$processedFile->getOriginalFile()->getProperty('height') 73 ); 74 } 75 if ($imageDimension->width <=0 || $imageDimension->height <=0) { 76 throw new \BadMethodCallException('Width and height of the image must be greater than zero', 1597310560); 77 } 78 $result = GeneralUtility::makeInstance(GraphicalFunctions::class)->getImageScale( 79 [ 80 $imageDimension->width, 81 $imageDimension->height, 82 $processedFile->getExtension() 83 ], 84 $config['width'] ?? null, 85 $config['height'] ?? null, 86 $config 87 ); 88 $imageWidth = $geometryWidth = (int)$result[0]; 89 $imageHeight = $geometryHeight = (int)$result[1]; 90 $isCropScaled = $result['crs']; 91 92 if ($isCropScaled) { 93 $cropWidth = (int)$result['origW']; 94 $cropHeight = (int)$result['origH']; 95 // If the image is crop scaled, use the dimension of the crop 96 // unless crop area exceeds the dimension of the scaled image 97 if ($cropWidth <= $geometryWidth && $cropHeight <= $geometryHeight) { 98 $imageWidth = $cropWidth; 99 $imageHeight = $cropHeight; 100 } 101 if (!$isCropped && $task->getTargetFileExtension() === 'svg') { 102 // Keep aspect ratio of SVG files, when crop-scaling is requested 103 // but no crop is applied 104 if ($geometryWidth > $geometryHeight) { 105 $imageHeight = (int)round($imageWidth * $geometryHeight / $geometryWidth); 106 } else { 107 $imageWidth = (int)round($imageHeight * $geometryWidth / $geometryHeight); 108 } 109 } 110 } 111 $imageDimension->width = $imageWidth; 112 $imageDimension->height = $imageHeight; 113 114 return $imageDimension; 115 } 116 117 private static function getConfigurationForImageCropScaleMask(TaskInterface $task): array 118 { 119 $configuration = $task->getConfiguration(); 120 121 if ($task->getTargetFile()->getTaskIdentifier() === ProcessedFile::CONTEXT_IMAGEPREVIEW) { 122 $configuration = LocalPreviewHelper::preProcessConfiguration($configuration); 123 $configuration['maxWidth'] = $configuration['width']; 124 unset($configuration['width']); 125 $configuration['maxHeight'] = $configuration['height']; 126 unset($configuration['height']); 127 } 128 129 $options = $configuration; 130 if ($configuration['maxWidth'] ?? null) { 131 $options['maxW'] = $configuration['maxWidth']; 132 } 133 if ($configuration['maxHeight'] ?? null) { 134 $options['maxH'] = $configuration['maxHeight']; 135 } 136 if ($configuration['minWidth'] ?? null) { 137 $options['minW'] = $configuration['minWidth']; 138 } 139 if ($configuration['minHeight'] ?? null) { 140 $options['minH'] = $configuration['minHeight']; 141 } 142 if ($configuration['crop'] ?? null) { 143 $options['crop'] = $configuration['crop']; 144 if (is_string($configuration['crop'])) { 145 // check if it is a json object 146 $cropData = json_decode($configuration['crop']); 147 if ($cropData) { 148 $options['crop'] = new Area($cropData->x, $cropData->y, $cropData->width, $cropData->height); 149 } else { 150 [$offsetLeft, $offsetTop, $newWidth, $newHeight] = explode(',', $configuration['crop'], 4); 151 $options['crop'] = new Area((float)$offsetLeft, (float)$offsetTop, (float)$newWidth, (float)$newHeight); 152 } 153 if ($options['crop']->isEmpty()) { 154 unset($options['crop']); 155 } 156 } 157 } 158 if ($configuration['noScale'] ?? null) { 159 $options['noScale'] = $configuration['noScale']; 160 } 161 162 return $options; 163 } 164} 165