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