]> git.mxchange.org Git - friendica.git/blob - src/Util/Images.php
Merge remote-tracking branch 'upstream/develop' into ocr
[friendica.git] / src / Util / Images.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2024, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
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.
11  *
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.
16  *
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/>.
19  *
20  */
21
22 namespace Friendica\Util;
23
24 use Friendica\Core\Logger;
25 use Friendica\Core\System;
26 use Friendica\DI;
27 use Friendica\Model\Photo;
28 use Friendica\Network\HTTPClient\Client\HttpClientAccept;
29 use Friendica\Object\Image;
30 use thiagoalessio\TesseractOCR\TesseractOCR;
31
32 /**
33  * Image utilities
34  */
35 class Images
36 {
37         /**
38          * Maps Mime types to Imagick formats
39          *
40          * @return array Format map
41          */
42         public static function getFormatsMap()
43         {
44                 return [
45                         'image/jpeg' => 'JPG',
46                         'image/jpg' => 'JPG',
47                         'image/png' => 'PNG',
48                         'image/gif' => 'GIF',
49                 ];
50         }
51
52         /**
53          * Return file extension for MIME type
54          *
55          * @param string $mimetype MIME type
56          * @return string File extension for MIME type
57          */
58         public static function getExtensionByMimeType(string $mimetype): string
59         {
60                 switch ($mimetype) {
61                         case 'image/png':
62                                 $imagetype = IMAGETYPE_PNG;
63                                 break;
64
65                         case 'image/gif':
66                                 $imagetype = IMAGETYPE_GIF;
67                                 break;
68
69                         case 'image/jpeg':
70                         case 'image/jpg':
71                                 $imagetype = IMAGETYPE_JPEG;
72                                 break;
73
74                         default: // Unknown type must be a blob then
75                                 return 'blob';
76                                 break;
77                 }
78
79                 return image_type_to_extension($imagetype);
80         }
81
82         /**
83          * Returns supported image mimetypes and corresponding file extensions
84          *
85          * @return array
86          */
87         public static function supportedTypes(): array
88         {
89                 $types = [
90                         'image/jpeg' => 'jpg',
91                         'image/jpg' => 'jpg',
92                 ];
93
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
97                         $types += [
98                                 'image/png' => 'png',
99                                 'image/gif' => 'gif'
100                         ];
101                 } elseif (imagetypes() & IMG_PNG) {
102                         $types += [
103                                 'image/png' => 'png'
104                         ];
105                 }
106
107                 return $types;
108         }
109
110         /**
111          * Fetch image mimetype from the image data or guessing from the file name
112          *
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
117          * @throws \Exception
118          */
119         public static function getMimeTypeByData(string $image_data, string $filename = '', string $default = ''): string
120         {
121                 if (substr($default, 0, 6) == 'image/') {
122                         Logger::info('Using default mime type', ['filename' => $filename, 'mime' => $default]);
123                         return $default;
124                 }
125
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'];
130                 }
131
132                 return self::guessTypeByExtension($filename);
133         }
134
135         /**
136          * Fetch image mimetype from the image data or guessing from the file name
137          *
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
142          * @throws \Exception
143          */
144         public static function getMimeTypeBySource(string $sourcefile, string $filename = '', string $default = ''): string
145         {
146                 if (substr($default, 0, 6) == 'image/') {
147                         Logger::info('Using default mime type', ['filename' => $filename, 'mime' => $default]);
148                         return $default;
149                 }
150
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'];
155                 }
156
157                 return self::guessTypeByExtension($filename);
158         }
159
160         /**
161          * Guess image MIME type from the filename's extension
162          *
163          * @param string $filename Image filename
164          * @return string Guessed MIME type by extension
165          * @throws \Exception
166          */
167         public static function guessTypeByExtension(string $filename): string
168         {
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) {
173                         if ($ext == $e) {
174                                 $type = $m;
175                         }
176                 }
177
178                 Logger::info('Mime type guessed via extension', ['filename' => $filename, 'type' => $type]);
179                 return $type;
180         }
181
182         /**
183          * Gets info array from given URL, cached data has priority
184          *
185          * @param string $url
186          * @param bool   $ocr
187          * @return array Info
188          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
189          */
190         public static function getInfoFromURLCached(string $url, bool $ocr = false): array
191         {
192                 $data = [];
193
194                 if (empty($url)) {
195                         return $data;
196                 }
197
198                 $cacheKey = 'getInfoFromURL:' . sha1($url . $ocr);
199
200                 $data = DI::cache()->get($cacheKey);
201
202                 if (empty($data) || !is_array($data)) {
203                         $data = self::getInfoFromURL($url, $ocr);
204
205                         DI::cache()->set($cacheKey, $data);
206                 }
207
208                 return $data ?? [];
209         }
210
211         /**
212          * Gets info from URL uncached
213          *
214          * @param string $url
215          * @param bool   $ocr
216          * @return array Info array
217          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
218          */
219         public static function getInfoFromURL(string $url, bool $ocr = false): array
220         {
221                 $data = [];
222
223                 if (empty($url)) {
224                         return $data;
225                 }
226
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);
231                         }
232                         // @todo Possibly add a check for locally stored files
233                 }
234
235                 if (empty($img_str)) {
236                         try {
237                                 $img_str = DI::httpClient()->fetch($url, HttpClientAccept::IMAGE, 4);
238                         } catch (\Exception $exception) {
239                                 Logger::notice('Image is invalid', ['url' => $url, 'exception' => $exception]);
240                                 return [];
241                         }
242                 }
243
244                 if (!$img_str) {
245                         return [];
246                 }
247
248                 $filesize = strlen($img_str);
249
250                 try {
251                         $data = @getimagesizefromstring($img_str);
252                 } catch (\Exception $e) {
253                         return [];
254                 }
255
256                 if (!$data) {
257                         return [];
258                 }
259
260                 $image = new Image($img_str);
261
262                 if ($image->isValid()) {
263                         $data['blurhash'] = $image->getBlurHash();
264                         
265                         if ($ocr && DI::config()->get('system', 'tesseract_ocr')) {
266                                 $ocr = new TesseractOCR();
267                                 try {
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()]);
273                                 }                       
274                         }
275                 }
276
277                 $data['size'] = $filesize;
278
279                 return $data;
280         }
281
282         /**
283          * Returns scaling information
284          *
285          * @param integer $width Width
286          * @param integer $height Height
287          * @param integer $max Max width/height
288          * @return array Scaling dimensions
289          */
290         public static function getScalingDimensions(int $width, int $height, int $max): array
291         {
292                 if ((!$width) || (!$height)) {
293                         return ['width' => 0, 'height' => 0];
294                 }
295
296                 if ($width > $max && $height > $max) {
297                         // very tall image (greater than 16:9)
298                         // constrain the width - let the height float.
299
300                         if ((($height * 9) / 16) > $width) {
301                                 $dest_width = $max;
302                                 $dest_height = intval(($height * $max) / $width);
303                         } elseif ($width > $height) {
304                                 // else constrain both dimensions
305                                 $dest_width = $max;
306                                 $dest_height = intval(($height * $max) / $width);
307                         } else {
308                                 $dest_width = intval(($width * $max) / $height);
309                                 $dest_height = $max;
310                         }
311                 } else {
312                         if ($width > $max) {
313                                 $dest_width = $max;
314                                 $dest_height = intval(($height * $max) / $width);
315                         } else {
316                                 if ($height > $max) {
317                                         // very tall image (greater than 16:9)
318                                         // but width is OK - don't do anything
319
320                                         if ((($height * 9) / 16) > $width) {
321                                                 $dest_width = $width;
322                                                 $dest_height = $height;
323                                         } else {
324                                                 $dest_width = intval(($width * $max) / $height);
325                                                 $dest_height = $max;
326                                         }
327                                 } else {
328                                         $dest_width = $width;
329                                         $dest_height = $height;
330                                 }
331                         }
332                 }
333
334                 return ['width' => $dest_width, 'height' => $dest_height];
335         }
336
337         /**
338          * Get a BBCode tag for an local photo page URL with a preview thumbnail and an image description
339          *
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
345          * @return string
346          */
347         public static function getBBCodeByResource(string $resource_id, string $nickname, int $preview, string $ext, string $description = ''): string
348         {
349                 return self::getBBCodeByUrl(
350                         DI::baseUrl() . '/photos/' . $nickname . '/image/' . $resource_id,
351                         DI::baseUrl() . '/photo/' . $resource_id . '-' . $preview. '.' . $ext,
352                         $description
353                 );
354         }
355
356         /**
357          * Get a BBCode tag for an image URL with a preview thumbnail and an image description
358          *
359          * @param string $photo Full image URL
360          * @param string $preview Preview image URL
361          * @param string $description
362          * @return string
363          */
364         public static function getBBCodeByUrl(string $photo, string $preview = null, string $description = ''): string
365         {
366                 if (!empty($preview)) {
367                         return '[url=' . $photo . '][img=' . $preview . ']' . $description . '[/img][/url]';
368                 }
369
370                 return '[img=' . $photo . ']' . $description . '[/img]';
371         }
372
373         /**
374          * Get the maximum possible upload size in bytes
375          *
376          * @return integer
377          */
378         public static function getMaxUploadBytes(): int
379         {
380                 $upload_size = ini_get('upload_max_filesize') ?: DI::config()->get('system', 'maximagesize');
381                 return Strings::getBytesFromShorthand($upload_size);
382         }
383 }