3 * @copyright Copyright (C) 2010-2024, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Util;
24 use Friendica\Core\Logger;
25 use Friendica\Core\System;
27 use Friendica\Model\Photo;
28 use Friendica\Network\HTTPClient\Client\HttpClientAccept;
29 use Friendica\Object\Image;
30 use thiagoalessio\TesseractOCR\TesseractOCR;
38 * Maps Mime types to Imagick formats
40 * @return array Format map
42 public static function getFormatsMap()
45 'image/jpeg' => 'JPG',
53 * Return file extension for MIME type
55 * @param string $mimetype MIME type
56 * @return string File extension for MIME type
58 public static function getExtensionByMimeType(string $mimetype): string
62 $imagetype = IMAGETYPE_PNG;
66 $imagetype = IMAGETYPE_GIF;
71 $imagetype = IMAGETYPE_JPEG;
74 default: // Unknown type must be a blob then
79 return image_type_to_extension($imagetype);
83 * Returns supported image mimetypes and corresponding file extensions
87 public static function supportedTypes(): array
90 'image/jpeg' => 'jpg',
94 if (class_exists('Imagick')) {
95 // Imagick::queryFormats won't help us a lot there...
96 // At least, not yet, other parts of friendica uses this array
101 } elseif (imagetypes() & IMG_PNG) {
111 * Fetch image mimetype from the image data or guessing from the file name
113 * @param string $image_data Image data
114 * @param string $filename File name (for guessing the type via the extension)
115 * @param string $default Default MIME type
116 * @return string MIME type
119 public static function getMimeTypeByData(string $image_data, string $filename = '', string $default = ''): string
121 if (substr($default, 0, 6) == 'image/') {
122 Logger::info('Using default mime type', ['filename' => $filename, 'mime' => $default]);
126 $image = @getimagesizefromstring($image_data);
127 if (!empty($image['mime'])) {
128 Logger::info('Mime type detected via data', ['filename' => $filename, 'default' => $default, 'mime' => $image['mime']]);
129 return $image['mime'];
132 return self::guessTypeByExtension($filename);
136 * Fetch image mimetype from the image data or guessing from the file name
138 * @param string $sourcefile Source file of the image
139 * @param string $filename File name (for guessing the type via the extension)
140 * @param string $default default MIME type
141 * @return string MIME type
144 public static function getMimeTypeBySource(string $sourcefile, string $filename = '', string $default = ''): string
146 if (substr($default, 0, 6) == 'image/') {
147 Logger::info('Using default mime type', ['filename' => $filename, 'mime' => $default]);
151 $image = @getimagesize($sourcefile);
152 if (!empty($image['mime'])) {
153 Logger::info('Mime type detected via file', ['filename' => $filename, 'default' => $default, 'image' => $image]);
154 return $image['mime'];
157 return self::guessTypeByExtension($filename);
161 * Guess image MIME type from the filename's extension
163 * @param string $filename Image filename
164 * @return string Guessed MIME type by extension
167 public static function guessTypeByExtension(string $filename): string
169 $ext = pathinfo(parse_url($filename, PHP_URL_PATH), PATHINFO_EXTENSION);
170 $types = self::supportedTypes();
171 $type = 'image/jpeg';
172 foreach ($types as $m => $e) {
178 Logger::info('Mime type guessed via extension', ['filename' => $filename, 'type' => $type]);
183 * Gets info array from given URL, cached data has priority
188 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
190 public static function getInfoFromURLCached(string $url, bool $ocr = false): array
198 $cacheKey = 'getInfoFromURL:' . sha1($url . $ocr);
200 $data = DI::cache()->get($cacheKey);
202 if (empty($data) || !is_array($data)) {
203 $data = self::getInfoFromURL($url, $ocr);
205 DI::cache()->set($cacheKey, $data);
212 * Gets info from URL uncached
216 * @return array Info array
217 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
219 public static function getInfoFromURL(string $url, bool $ocr = false): array
227 if (Network::isLocalLink($url) && ($data = Photo::getResourceData($url))) {
228 $photo = Photo::selectFirst([], ['resource-id' => $data['guid'], 'scale' => $data['scale']]);
229 if (!empty($photo)) {
230 $img_str = Photo::getImageDataForPhoto($photo);
232 // @todo Possibly add a check for locally stored files
235 if (empty($img_str)) {
237 $img_str = DI::httpClient()->fetch($url, HttpClientAccept::IMAGE, 4);
238 } catch (\Exception $exception) {
239 Logger::notice('Image is invalid', ['url' => $url, 'exception' => $exception]);
248 $filesize = strlen($img_str);
251 $data = @getimagesizefromstring($img_str);
252 } catch (\Exception $e) {
260 $image = new Image($img_str);
262 if ($image->isValid()) {
263 $data['blurhash'] = $image->getBlurHash();
265 if ($ocr && DI::config()->get('system', 'tesseract_ocr')) {
266 $ocr = new TesseractOCR();
268 $ocr->tempDir(System::getTempPath());
269 $ocr->imageData($img_str, strlen($img_str));
270 $data['description'] = $ocr->run();
271 } catch (\Throwable $th) {
272 Logger::info('Error calling TesseractOCR', ['message' => $th->getMessage()]);
277 $data['size'] = $filesize;
283 * Returns scaling information
285 * @param integer $width Width
286 * @param integer $height Height
287 * @param integer $max Max width/height
288 * @return array Scaling dimensions
290 public static function getScalingDimensions(int $width, int $height, int $max): array
292 if ((!$width) || (!$height)) {
293 return ['width' => 0, 'height' => 0];
296 if ($width > $max && $height > $max) {
297 // very tall image (greater than 16:9)
298 // constrain the width - let the height float.
300 if ((($height * 9) / 16) > $width) {
302 $dest_height = intval(($height * $max) / $width);
303 } elseif ($width > $height) {
304 // else constrain both dimensions
306 $dest_height = intval(($height * $max) / $width);
308 $dest_width = intval(($width * $max) / $height);
314 $dest_height = intval(($height * $max) / $width);
316 if ($height > $max) {
317 // very tall image (greater than 16:9)
318 // but width is OK - don't do anything
320 if ((($height * 9) / 16) > $width) {
321 $dest_width = $width;
322 $dest_height = $height;
324 $dest_width = intval(($width * $max) / $height);
328 $dest_width = $width;
329 $dest_height = $height;
334 return ['width' => $dest_width, 'height' => $dest_height];
338 * Get a BBCode tag for an local photo page URL with a preview thumbnail and an image description
340 * @param string $resource_id
341 * @param string $nickname The local user owner of the resource
342 * @param int $preview Preview image size identifier, either 0, 1 or 2 in decreasing order of size
343 * @param string $ext Image file extension
344 * @param string $description
347 public static function getBBCodeByResource(string $resource_id, string $nickname, int $preview, string $ext, string $description = ''): string
349 return self::getBBCodeByUrl(
350 DI::baseUrl() . '/photos/' . $nickname . '/image/' . $resource_id,
351 DI::baseUrl() . '/photo/' . $resource_id . '-' . $preview. '.' . $ext,
357 * Get a BBCode tag for an image URL with a preview thumbnail and an image description
359 * @param string $photo Full image URL
360 * @param string $preview Preview image URL
361 * @param string $description
364 public static function getBBCodeByUrl(string $photo, string $preview = null, string $description = ''): string
366 if (!empty($preview)) {
367 return '[url=' . $photo . '][img=' . $preview . ']' . $description . '[/img][/url]';
370 return '[img=' . $photo . ']' . $description . '[/img]';
374 * Get the maximum possible upload size in bytes
378 public static function getMaxUploadBytes(): int
380 $upload_size = ini_get('upload_max_filesize') ?: DI::config()->get('system', 'maximagesize');
381 return Strings::getBytesFromShorthand($upload_size);