Elgg /
Elgg
Check for loose comparison of boolean to boolean.
| 1 | <?php |
||
| 2 | |||
| 3 | namespace Elgg; |
||
| 4 | |||
| 5 | use Exception; |
||
| 6 | use Imagine\Image\Box; |
||
| 7 | use Imagine\Image\ImagineInterface; |
||
| 8 | use Imagine\Image\Point; |
||
| 9 | use Imagine\Filter\Basic\Autorotate; |
||
| 10 | use Elgg\Filesystem\MimeTypeDetector; |
||
| 11 | |||
| 12 | /** |
||
| 13 | * Image manipulation service |
||
| 14 | * |
||
| 15 | * @since 2.3 |
||
| 16 | * @access private |
||
| 17 | */ |
||
| 18 | class ImageService { |
||
| 19 | use Loggable; |
||
| 20 | |||
| 21 | const JPEG_QUALITY = 75; |
||
| 22 | |||
| 23 | /** |
||
| 24 | * @var ImagineInterface |
||
| 25 | */ |
||
| 26 | private $imagine; |
||
| 27 | |||
| 28 | /** |
||
| 29 | * @var Config |
||
| 30 | */ |
||
| 31 | private $config; |
||
| 32 | |||
| 33 | /** |
||
| 34 | * Constructor |
||
| 35 | * |
||
| 36 | * @param ImagineInterface $imagine Imagine interface |
||
| 37 | * @param Config $config Elgg config |
||
| 38 | */ |
||
| 39 | 72 | public function __construct(ImagineInterface $imagine, Config $config) { |
|
| 40 | 72 | $this->imagine = $imagine; |
|
| 41 | 72 | $this->config = $config; |
|
| 42 | 72 | } |
|
| 43 | |||
| 44 | /** |
||
| 45 | * Crop and resize an image |
||
| 46 | * |
||
| 47 | * @param string $source Path to source image |
||
| 48 | * @param string $destination Path to destination |
||
| 49 | * If not set, will modify the source image |
||
| 50 | * @param array $params An array of cropping/resizing parameters |
||
| 51 | * - INT 'w' represents the width of the new image |
||
| 52 | * With upscaling disabled, this is the maximum width |
||
| 53 | * of the new image (in case the source image is |
||
| 54 | * smaller than the expected width) |
||
| 55 | * - INT 'h' represents the height of the new image |
||
| 56 | * With upscaling disabled, this is the maximum height |
||
| 57 | * - INT 'x1', 'y1', 'x2', 'y2' represent optional cropping |
||
| 58 | * coordinates. The source image will first be cropped |
||
| 59 | * to these coordinates, and then resized to match |
||
| 60 | * width/height parameters |
||
| 61 | * - BOOL 'square' - square images will fill the |
||
| 62 | * bounding box (width x height). In Imagine's terms, |
||
| 63 | * this equates to OUTBOUND mode |
||
| 64 | * - BOOL 'upscale' - if enabled, smaller images |
||
| 65 | * will be upscaled to fit the bounding box. |
||
| 66 | * @return bool |
||
| 67 | */ |
||
| 68 | 47 | public function resize($source, $destination = null, array $params = []) { |
|
| 69 | |||
| 70 | 47 | if (!isset($destination)) { |
|
| 71 | $destination = $source; |
||
| 72 | } |
||
| 73 | |||
| 74 | try { |
||
| 75 | 47 | $image = $this->imagine->open($source); |
|
| 76 | |||
| 77 | 47 | $width = $image->getSize()->getWidth(); |
|
| 78 | 47 | $height = $image->getSize()->getHeight(); |
|
| 79 | |||
| 80 | 47 | $resize_params = $this->normalizeResizeParameters($width, $height, $params); |
|
| 81 | |||
| 82 | 47 | $max_width = elgg_extract('w', $resize_params); |
|
| 83 | 47 | $max_height = elgg_extract('h', $resize_params); |
|
| 84 | |||
| 85 | 47 | $x1 = (int) elgg_extract('x1', $resize_params, 0); |
|
| 86 | 47 | $y1 = (int) elgg_extract('y1', $resize_params, 0); |
|
| 87 | 47 | $x2 = (int) elgg_extract('x2', $resize_params, 0); |
|
| 88 | 47 | $y2 = (int) elgg_extract('y2', $resize_params, 0); |
|
| 89 | |||
| 90 | 47 | if ($x2 > $x1 && $y2 > $y1) { |
|
| 91 | 47 | $crop_start = new Point($x1, $y1); |
|
| 92 | 47 | $crop_size = new Box($x2 - $x1, $y2 - $y1); |
|
| 93 | 47 | $image->crop($crop_start, $crop_size); |
|
| 94 | } |
||
| 95 | |||
| 96 | 47 | $target_size = new Box($max_width, $max_height); |
|
| 97 | 47 | $thumbnail = $image->resize($target_size); |
|
| 98 | |||
| 99 | 47 | $thumbnail->save($destination, [ |
|
| 100 | 47 | 'jpeg_quality' => elgg_extract('jpeg_quality', $params, self::JPEG_QUALITY), |
|
| 101 | 47 | 'format' => $this->getFileFormat($source, $params), |
|
| 102 | ]); |
||
| 103 | |||
| 104 | 47 | unset($image); |
|
| 105 | 47 | unset($thumbnail); |
|
| 106 | } catch (Exception $ex) { |
||
| 107 | $logger = $this->logger ? $this->logger : _elgg_services()->logger; |
||
| 108 | $logger->error($ex->getMessage()); |
||
| 109 | return false; |
||
| 110 | } |
||
| 111 | |||
| 112 | 47 | return true; |
|
| 113 | } |
||
| 114 | |||
| 115 | /** |
||
| 116 | * If needed the image will be rotated based on orientation information |
||
| 117 | * |
||
| 118 | * @param string $filename Path to image |
||
| 119 | * |
||
| 120 | * @return bool |
||
| 121 | */ |
||
| 122 | 5 | function fixOrientation($filename) { |
|
| 123 | try { |
||
| 124 | 5 | $image = $this->imagine->open($filename); |
|
| 125 | 5 | $metadata = $image->metadata(); |
|
| 126 | 5 | if (!isset($metadata['ifd0.Orientation'])) { |
|
| 127 | // no need to perform an orientation fix |
||
| 128 | 5 | return true; |
|
| 129 | } |
||
| 130 | |||
| 131 | $autorotate = new Autorotate(); |
||
| 132 | $autorotate->apply($image)->save($filename); |
||
| 133 | return true; |
||
| 134 | } catch (Exception $ex) { |
||
| 135 | $logger = $this->logger ? $this->logger : _elgg_services()->logger; |
||
| 136 | $logger->notice($ex->getMessage()); |
||
| 137 | } |
||
| 138 | return false; |
||
| 139 | } |
||
| 140 | |||
| 141 | /** |
||
| 142 | * Calculate the parameters for resizing an image |
||
| 143 | * |
||
| 144 | * @param int $width Natural width of the image |
||
| 145 | * @param int $height Natural height of the image |
||
| 146 | * @param array $params Resize parameters |
||
| 147 | * - 'w' maximum width of the resized image |
||
| 148 | * - 'h' maximum height of the resized image |
||
| 149 | * - 'upscale' allow upscaling |
||
| 150 | * - 'square' constrain to a square |
||
| 151 | * - 'x1', 'y1', 'x2', 'y2' cropping coordinates |
||
| 152 | * |
||
| 153 | * @return array |
||
| 154 | * @throws \LogicException |
||
| 155 | */ |
||
| 156 | 48 | public function normalizeResizeParameters($width, $height, array $params = []) { |
|
| 157 | |||
| 158 | 48 | $max_width = (int) elgg_extract('w', $params, 100, false); |
|
| 159 | 48 | $max_height = (int) elgg_extract('h', $params, 100, false); |
|
| 160 | 48 | if (!$max_height || !$max_width) { |
|
| 161 | throw new \LogicException("Resize width and height parameters are required"); |
||
| 162 | } |
||
| 163 | |||
| 164 | 48 | $square = elgg_extract('square', $params, false); |
|
| 165 | 48 | $upscale = elgg_extract('upscale', $params, false); |
|
| 166 | |||
| 167 | 48 | $x1 = (int) elgg_extract('x1', $params, 0); |
|
| 168 | 48 | $y1 = (int) elgg_extract('y1', $params, 0); |
|
| 169 | 48 | $x2 = (int) elgg_extract('x2', $params, 0); |
|
| 170 | 48 | $y2 = (int) elgg_extract('y2', $params, 0); |
|
| 171 | |||
| 172 | 48 | $cropping_mode = $x1 || $y1 || $x2 || $y2; |
|
| 173 | |||
| 174 | 48 | if ($cropping_mode) { |
|
| 175 | 8 | $crop_width = $x2 - $x1; |
|
| 176 | 8 | $crop_height = $y2 - $y1; |
|
| 177 | 8 | if ($crop_width <= 0 || $crop_height <= 0 || $crop_width > $width || $crop_height > $height) { |
|
| 178 | 8 | throw new \LogicException("Coordinates [$x1, $y1], [$x2, $y2] are invalid for image cropping"); |
|
| 179 | } |
||
| 180 | } else { |
||
| 181 | // everything selected if no crop parameters |
||
| 182 | 47 | $crop_width = $width; |
|
| 183 | 47 | $crop_height = $height; |
|
| 184 | } |
||
| 185 | |||
| 186 | // determine cropping offsets |
||
| 187 | 48 | if ($square) { |
|
| 188 | // asking for a square image back |
||
| 189 | // detect case where someone is passing crop parameters that are not for a square |
||
| 190 | 40 | if ($cropping_mode == true && $crop_width != $crop_height) { |
|
|
0 ignored issues
–
show
|
|||
| 191 | throw new \LogicException("Coordinates [$x1, $y1], [$x2, $y2] are invalid for a squared image cropping"); |
||
| 192 | } |
||
| 193 | |||
| 194 | // size of the new square image |
||
| 195 | 40 | $max_width = $max_height = min($max_width, $max_height); |
|
| 196 | |||
| 197 | // find largest square that fits within the selected region |
||
| 198 | 40 | $crop_width = $crop_height = min($crop_width, $crop_height); |
|
| 199 | |||
| 200 | 40 | if (!$cropping_mode) { |
|
| 201 | // place square region in the center |
||
| 202 | 39 | $x1 = floor(($width - $crop_width) / 2); |
|
| 203 | 40 | $y1 = floor(($height - $crop_height) / 2); |
|
| 204 | } |
||
| 205 | } else { |
||
| 206 | // maintain aspect ratio of original image/crop |
||
| 207 | 44 | if ($crop_height / $max_height > $crop_width / $max_width) { |
|
| 208 | 20 | $max_width = floor($max_height * $crop_width / $crop_height); |
|
| 209 | } else { |
||
| 210 | 26 | $max_height = floor($max_width * $crop_height / $crop_width); |
|
| 211 | } |
||
| 212 | } |
||
| 213 | |||
| 214 | 48 | if (!$upscale && ($crop_height < $max_height || $crop_width < $max_width)) { |
|
| 215 | // we cannot upscale and selected area is too small so we decrease size of returned image |
||
| 216 | 45 | $max_height = $crop_height; |
|
| 217 | 45 | $max_width = $crop_width; |
|
| 218 | } |
||
| 219 | |||
| 220 | return [ |
||
| 221 | 48 | 'w' => $max_width, |
|
| 222 | 48 | 'h' => $max_height, |
|
| 223 | 48 | 'x1' => $x1, |
|
| 224 | 48 | 'y1' => $y1, |
|
| 225 | 48 | 'x2' => $x1 + $crop_width, |
|
| 226 | 48 | 'y2' => $y1 + $crop_height, |
|
| 227 | 48 | 'square' => $square, |
|
| 228 | 48 | 'upscale' => $upscale, |
|
| 229 | ]; |
||
| 230 | } |
||
| 231 | |||
| 232 | /** |
||
| 233 | * Determine the image file format, this is needed for correct resizing |
||
| 234 | * |
||
| 235 | * @param string $filename path to the file |
||
| 236 | * @param array $params array of resizing params (can contain 'format' to set save format) |
||
| 237 | * |
||
| 238 | * @see https://github.com/Elgg/Elgg/issues/10686 |
||
| 239 | * @return void|string |
||
| 240 | */ |
||
| 241 | 47 | protected function getFileFormat($filename, $params) { |
|
| 242 | |||
| 243 | $accepted_formats = [ |
||
| 244 | 47 | 'image/jpeg' => 'jpeg', |
|
| 245 | 'image/pjpeg' => 'jpeg', |
||
| 246 | 'image/png' => 'png', |
||
| 247 | 'image/x-png' => 'png', |
||
| 248 | 'image/gif' => 'gif', |
||
| 249 | 'image/vnd.wap.wbmp' => 'wbmp', |
||
| 250 | 'image/x‑xbitmap' => 'xbm', |
||
| 251 | 'image/x‑xbm' => 'xbm', |
||
| 252 | ]; |
||
| 253 | |||
| 254 | // was a valid output format supplied |
||
| 255 | 47 | $format = elgg_extract('format', $params); |
|
| 256 | 47 | if (in_array($format, $accepted_formats)) { |
|
| 257 | return $format; |
||
| 258 | } |
||
| 259 | |||
| 260 | 47 | $mime_detector = new MimeTypeDetector(); |
|
| 261 | 47 | $mime = $mime_detector->getType($filename); |
|
| 262 | |||
| 263 | 47 | return elgg_extract($mime, $accepted_formats); |
|
| 264 | } |
||
| 265 | } |
||
| 266 |
When comparing two booleans, it is generally considered safer to use the strict comparison operator.