]> git.mxchange.org Git - friendica.git/blob - src/Object/Image.php
Refactor namespaces
[friendica.git] / src / Object / Image.php
1 <?php
2 /**
3  * @file src/Object/Image.php
4  * @brief This file contains the Image class for image processing
5  */
6 namespace Friendica\Object;
7
8 use Friendica\App;
9 use Friendica\Core\Cache;
10 use Friendica\Core\Config;
11 use Friendica\Core\L10n;
12 use Friendica\Core\Logger;
13 use Friendica\Core\System;
14 use Friendica\Database\DBA;
15 use Friendica\Model\Photo;
16 use Friendica\Util\Network;
17 use Exception;
18 use Imagick;
19 use ImagickPixel;
20
21 /**
22  * Class to handle images
23  */
24 class Image
25 {
26         private $image;
27
28         /*
29          * Put back gd stuff, not everybody have Imagick
30          */
31         private $imagick;
32         private $width;
33         private $height;
34         private $valid;
35         private $type;
36         private $types;
37
38         /**
39          * @brief supported mimetypes and corresponding file extensions
40          * @return array
41          */
42         public static function supportedTypes()
43         {
44                 if (class_exists('Imagick')) {
45                         // Imagick::queryFormats won't help us a lot there...
46                         // At least, not yet, other parts of friendica uses this array
47                         $t = [
48                                 'image/jpeg' => 'jpg',
49                                 'image/png' => 'png',
50                                 'image/gif' => 'gif'
51                         ];
52                 } else {
53                         $t = [];
54                         $t['image/jpeg'] ='jpg';
55                         if (imagetypes() & IMG_PNG) {
56                                 $t['image/png'] = 'png';
57                         }
58                 }
59
60                 return $t;
61         }
62
63         /**
64          * @brief Constructor
65          * @param string  $data
66          * @param boolean $type optional, default null
67          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
68          * @throws \ImagickException
69          */
70         public function __construct($data, $type = null)
71         {
72                 $this->imagick = class_exists('Imagick');
73                 $this->types = static::supportedTypes();
74                 if (!array_key_exists($type, $this->types)) {
75                         $type='image/jpeg';
76                 }
77                 $this->type = $type;
78
79                 if ($this->isImagick() && $this->loadData($data)) {
80                         return true;
81                 } else {
82                         // Failed to load with Imagick, fallback
83                         $this->imagick = false;
84                 }
85                 return $this->loadData($data);
86         }
87
88         /**
89          * @brief Destructor
90          * @return void
91          */
92         public function __destruct()
93         {
94                 if ($this->image) {
95                         if ($this->isImagick()) {
96                                 $this->image->clear();
97                                 $this->image->destroy();
98                                 return;
99                         }
100                         if (is_resource($this->image)) {
101                                 imagedestroy($this->image);
102                         }
103                 }
104         }
105
106         /**
107          * @return boolean
108          */
109         public function isImagick()
110         {
111                 return $this->imagick;
112         }
113
114         /**
115          * @brief Maps Mime types to Imagick formats
116          * @return array With with image formats (mime type as key)
117          */
118         public static function getFormatsMap()
119         {
120                 $m = [
121                         'image/jpeg' => 'JPG',
122                         'image/png' => 'PNG',
123                         'image/gif' => 'GIF'
124                 ];
125                 return $m;
126         }
127
128         /**
129          * @param string $data data
130          * @return boolean
131          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
132          * @throws \ImagickException
133          */
134         private function loadData($data)
135         {
136                 if ($this->isImagick()) {
137                         $this->image = new Imagick();
138                         try {
139                                 $this->image->readImageBlob($data);
140                         } catch (Exception $e) {
141                                 // Imagick couldn't use the data
142                                 return false;
143                         }
144
145                         /*
146                          * Setup the image to the format it will be saved to
147                          */
148                         $map = self::getFormatsMap();
149                         $format = $map[$this->type];
150                         $this->image->setFormat($format);
151
152                         // Always coalesce, if it is not a multi-frame image it won't hurt anyway
153                         $this->image = $this->image->coalesceImages();
154
155                         /*
156                          * setup the compression here, so we'll do it only once
157                          */
158                         switch ($this->getType()) {
159                                 case "image/png":
160                                         $quality = Config::get('system', 'png_quality');
161                                         if ((! $quality) || ($quality > 9)) {
162                                                 $quality = PNG_QUALITY;
163                                         }
164                                         /*
165                                          * From http://www.imagemagick.org/script/command-line-options.php#quality:
166                                          *
167                                          * 'For the MNG and PNG image formats, the quality value sets
168                                          * the zlib compression level (quality / 10) and filter-type (quality % 10).
169                                          * The default PNG "quality" is 75, which means compression level 7 with adaptive PNG filtering,
170                                          * unless the image has a color map, in which case it means compression level 7 with no PNG filtering'
171                                          */
172                                         $quality = $quality * 10;
173                                         $this->image->setCompressionQuality($quality);
174                                         break;
175                                 case "image/jpeg":
176                                         $quality = Config::get('system', 'jpeg_quality');
177                                         if ((! $quality) || ($quality > 100)) {
178                                                 $quality = JPEG_QUALITY;
179                                         }
180                                         $this->image->setCompressionQuality($quality);
181                         }
182
183                         // The 'width' and 'height' properties are only used by non-Imagick routines.
184                         $this->width  = $this->image->getImageWidth();
185                         $this->height = $this->image->getImageHeight();
186                         $this->valid  = true;
187
188                         return true;
189                 }
190
191                 $this->valid = false;
192                 $this->image = @imagecreatefromstring($data);
193                 if ($this->image !== false) {
194                         $this->width  = imagesx($this->image);
195                         $this->height = imagesy($this->image);
196                         $this->valid  = true;
197                         imagealphablending($this->image, false);
198                         imagesavealpha($this->image, true);
199
200                         return true;
201                 }
202
203                 return false;
204         }
205
206         /**
207          * @return boolean
208          */
209         public function isValid()
210         {
211                 if ($this->isImagick()) {
212                         return ($this->image !== false);
213                 }
214                 return $this->valid;
215         }
216
217         /**
218          * @return mixed
219          */
220         public function getWidth()
221         {
222                 if (!$this->isValid()) {
223                         return false;
224                 }
225
226                 if ($this->isImagick()) {
227                         return $this->image->getImageWidth();
228                 }
229                 return $this->width;
230         }
231
232         /**
233          * @return mixed
234          */
235         public function getHeight()
236         {
237                 if (!$this->isValid()) {
238                         return false;
239                 }
240
241                 if ($this->isImagick()) {
242                         return $this->image->getImageHeight();
243                 }
244                 return $this->height;
245         }
246
247         /**
248          * @return mixed
249          */
250         public function getImage()
251         {
252                 if (!$this->isValid()) {
253                         return false;
254                 }
255
256                 if ($this->isImagick()) {
257                         /* Clean it */
258                         $this->image = $this->image->deconstructImages();
259                         return $this->image;
260                 }
261                 return $this->image;
262         }
263
264         /**
265          * @return mixed
266          */
267         public function getType()
268         {
269                 if (!$this->isValid()) {
270                         return false;
271                 }
272
273                 return $this->type;
274         }
275
276         /**
277          * @return mixed
278          */
279         public function getExt()
280         {
281                 if (!$this->isValid()) {
282                         return false;
283                 }
284
285                 return $this->types[$this->getType()];
286         }
287
288         /**
289          * @param integer $max max dimension
290          * @return mixed
291          */
292         public function scaleDown($max)
293         {
294                 if (!$this->isValid()) {
295                         return false;
296                 }
297
298                 $width = $this->getWidth();
299                 $height = $this->getHeight();
300
301                 $dest_width = $dest_height = 0;
302
303                 if ((! $width)|| (! $height)) {
304                         return false;
305                 }
306
307                 if ($width > $max && $height > $max) {
308                         // very tall image (greater than 16:9)
309                         // constrain the width - let the height float.
310
311                         if ((($height * 9) / 16) > $width) {
312                                 $dest_width = $max;
313                                 $dest_height = intval(($height * $max) / $width);
314                         } elseif ($width > $height) {
315                                 // else constrain both dimensions
316                                 $dest_width = $max;
317                                 $dest_height = intval(($height * $max) / $width);
318                         } else {
319                                 $dest_width = intval(($width * $max) / $height);
320                                 $dest_height = $max;
321                         }
322                 } else {
323                         if ($width > $max) {
324                                 $dest_width = $max;
325                                 $dest_height = intval(($height * $max) / $width);
326                         } else {
327                                 if ($height > $max) {
328                                         // very tall image (greater than 16:9)
329                                         // but width is OK - don't do anything
330
331                                         if ((($height * 9) / 16) > $width) {
332                                                 $dest_width = $width;
333                                                 $dest_height = $height;
334                                         } else {
335                                                 $dest_width = intval(($width * $max) / $height);
336                                                 $dest_height = $max;
337                                         }
338                                 } else {
339                                         $dest_width = $width;
340                                         $dest_height = $height;
341                                 }
342                         }
343                 }
344
345                 return $this->scale($dest_width, $dest_height);
346         }
347
348         /**
349          * @param integer $degrees degrees to rotate image
350          * @return mixed
351          */
352         public function rotate($degrees)
353         {
354                 if (!$this->isValid()) {
355                         return false;
356                 }
357
358                 if ($this->isImagick()) {
359                         $this->image->setFirstIterator();
360                         do {
361                                 $this->image->rotateImage(new ImagickPixel(), -$degrees); // ImageMagick rotates in the opposite direction of imagerotate()
362                         } while ($this->image->nextImage());
363                         return;
364                 }
365
366                 // if script dies at this point check memory_limit setting in php.ini
367                 $this->image  = imagerotate($this->image, $degrees, 0);
368                 $this->width  = imagesx($this->image);
369                 $this->height = imagesy($this->image);
370         }
371
372         /**
373          * @param boolean $horiz optional, default true
374          * @param boolean $vert  optional, default false
375          * @return mixed
376          */
377         public function flip($horiz = true, $vert = false)
378         {
379                 if (!$this->isValid()) {
380                         return false;
381                 }
382
383                 if ($this->isImagick()) {
384                         $this->image->setFirstIterator();
385                         do {
386                                 if ($horiz) {
387                                         $this->image->flipImage();
388                                 }
389                                 if ($vert) {
390                                         $this->image->flopImage();
391                                 }
392                         } while ($this->image->nextImage());
393                         return;
394                 }
395
396                 $w = imagesx($this->image);
397                 $h = imagesy($this->image);
398                 $flipped = imagecreate($w, $h);
399                 if ($horiz) {
400                         for ($x = 0; $x < $w; $x++) {
401                                 imagecopy($flipped, $this->image, $x, 0, $w - $x - 1, 0, 1, $h);
402                         }
403                 }
404                 if ($vert) {
405                         for ($y = 0; $y < $h; $y++) {
406                                 imagecopy($flipped, $this->image, 0, $y, 0, $h - $y - 1, $w, 1);
407                         }
408                 }
409                 $this->image = $flipped;
410         }
411
412         /**
413          * @param string $filename filename
414          * @return mixed
415          */
416         public function orient($filename)
417         {
418                 if ($this->isImagick()) {
419                         // based off comment on http://php.net/manual/en/imagick.getimageorientation.php
420                         $orientation = $this->image->getImageOrientation();
421                         switch ($orientation) {
422                                 case Imagick::ORIENTATION_BOTTOMRIGHT:
423                                         $this->image->rotateimage("#000", 180);
424                                         break;
425                                 case Imagick::ORIENTATION_RIGHTTOP:
426                                         $this->image->rotateimage("#000", 90);
427                                         break;
428                                 case Imagick::ORIENTATION_LEFTBOTTOM:
429                                         $this->image->rotateimage("#000", -90);
430                                         break;
431                         }
432
433                         $this->image->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
434                         return true;
435                 }
436                 // based off comment on http://php.net/manual/en/function.imagerotate.php
437
438                 if (!$this->isValid()) {
439                         return false;
440                 }
441
442                 if ((!function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg')) {
443                         return;
444                 }
445
446                 $exif = @exif_read_data($filename, null, true);
447                 if (!$exif) {
448                         return;
449                 }
450
451                 $ort = $exif['IFD0']['Orientation'];
452
453                 switch ($ort) {
454                         case 1: // nothing
455                                 break;
456
457                         case 2: // horizontal flip
458                                 $this->flip();
459                                 break;
460
461                         case 3: // 180 rotate left
462                                 $this->rotate(180);
463                                 break;
464
465                         case 4: // vertical flip
466                                 $this->flip(false, true);
467                                 break;
468
469                         case 5: // vertical flip + 90 rotate right
470                                 $this->flip(false, true);
471                                 $this->rotate(-90);
472                                 break;
473
474                         case 6: // 90 rotate right
475                                 $this->rotate(-90);
476                                 break;
477
478                         case 7: // horizontal flip + 90 rotate right
479                                 $this->flip();
480                                 $this->rotate(-90);
481                                 break;
482
483                         case 8: // 90 rotate left
484                                 $this->rotate(90);
485                                 break;
486                 }
487
488                 //      Logger::log('exif: ' . print_r($exif,true));
489                 return $exif;
490         }
491
492         /**
493          * @param integer $min minimum dimension
494          * @return mixed
495          */
496         public function scaleUp($min)
497         {
498                 if (!$this->isValid()) {
499                         return false;
500                 }
501
502                 $width = $this->getWidth();
503                 $height = $this->getHeight();
504
505                 $dest_width = $dest_height = 0;
506
507                 if ((!$width)|| (!$height)) {
508                         return false;
509                 }
510
511                 if ($width < $min && $height < $min) {
512                         if ($width > $height) {
513                                 $dest_width = $min;
514                                 $dest_height = intval(($height * $min) / $width);
515                         } else {
516                                 $dest_width = intval(($width * $min) / $height);
517                                 $dest_height = $min;
518                         }
519                 } else {
520                         if ($width < $min) {
521                                 $dest_width = $min;
522                                 $dest_height = intval(($height * $min) / $width);
523                         } else {
524                                 if ($height < $min) {
525                                         $dest_width = intval(($width * $min) / $height);
526                                         $dest_height = $min;
527                                 } else {
528                                         $dest_width = $width;
529                                         $dest_height = $height;
530                                 }
531                         }
532                 }
533
534                 return $this->scale($dest_width, $dest_height);
535         }
536
537         /**
538          * @param integer $dim dimension
539          * @return mixed
540          */
541         public function scaleToSquare($dim)
542         {
543                 if (!$this->isValid()) {
544                         return false;
545                 }
546
547                 return $this->scale($dim, $dim);
548         }
549
550         /**
551          * @brief Scale image to target dimensions
552          *
553          * @param int $dest_width
554          * @param int $dest_height
555          * @return boolean
556          */
557         private function scale($dest_width, $dest_height)
558         {
559                 if (!$this->isValid()) {
560                         return false;
561                 }
562
563                 if ($this->isImagick()) {
564                         /*
565                          * If it is not animated, there will be only one iteration here,
566                          * so don't bother checking
567                          */
568                         // Don't forget to go back to the first frame
569                         $this->image->setFirstIterator();
570                         do {
571                                 // FIXME - implement horizontal bias for scaling as in following GD functions
572                                 // to allow very tall images to be constrained only horizontally.
573                                 $this->image->scaleImage($dest_width, $dest_height);
574                         } while ($this->image->nextImage());
575
576                         // These may not be necessary anymore
577                         $this->width  = $this->image->getImageWidth();
578                         $this->height = $this->image->getImageHeight();
579                 } else {
580                         $dest = imagecreatetruecolor($dest_width, $dest_height);
581                         imagealphablending($dest, false);
582                         imagesavealpha($dest, true);
583
584                         if ($this->type=='image/png') {
585                                 imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha
586                         }
587
588                         imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $this->width, $this->height);
589
590                         if ($this->image) {
591                                 imagedestroy($this->image);
592                         }
593
594                         $this->image = $dest;
595                         $this->width  = imagesx($this->image);
596                         $this->height = imagesy($this->image);
597                 }
598
599                 return true;
600         }
601
602         /**
603          * @param integer $max maximum
604          * @param integer $x   x coordinate
605          * @param integer $y   y coordinate
606          * @param integer $w   width
607          * @param integer $h   height
608          * @return mixed
609          */
610         public function crop($max, $x, $y, $w, $h)
611         {
612                 if (!$this->isValid()) {
613                         return false;
614                 }
615
616                 if ($this->isImagick()) {
617                         $this->image->setFirstIterator();
618                         do {
619                                 $this->image->cropImage($w, $h, $x, $y);
620                                 /*
621                                  * We need to remove the canva,
622                                  * or the image is not resized to the crop:
623                                  * http://php.net/manual/en/imagick.cropimage.php#97232
624                                  */
625                                 $this->image->setImagePage(0, 0, 0, 0);
626                         } while ($this->image->nextImage());
627                         return $this->scaleDown($max);
628                 }
629
630                 $dest = imagecreatetruecolor($max, $max);
631                 imagealphablending($dest, false);
632                 imagesavealpha($dest, true);
633                 if ($this->type=='image/png') {
634                         imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha
635                 }
636                 imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h);
637                 if ($this->image) {
638                         imagedestroy($this->image);
639                 }
640                 $this->image = $dest;
641                 $this->width  = imagesx($this->image);
642                 $this->height = imagesy($this->image);
643         }
644
645         /**
646          * @param string $path file path
647          * @return mixed
648          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
649          */
650         public function saveToFilePath($path)
651         {
652                 if (!$this->isValid()) {
653                         return false;
654                 }
655
656                 $string = $this->asString();
657
658                 $a = \get_app();
659
660                 $stamp1 = microtime(true);
661                 file_put_contents($path, $string);
662                 $a->saveTimestamp($stamp1, "file");
663         }
664
665         /**
666          * @brief Magic method allowing string casting of an Image object
667          *
668          * Ex: $data = $Image->asString();
669          * can be replaced by
670          * $data = (string) $Image;
671          *
672          * @return string
673          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
674          */
675         public function __toString() {
676                 return $this->asString();
677         }
678
679         /**
680          * @return mixed
681          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
682          */
683         public function asString()
684         {
685                 if (!$this->isValid()) {
686                         return false;
687                 }
688
689                 if ($this->isImagick()) {
690                         /* Clean it */
691                         $this->image = $this->image->deconstructImages();
692                         $string = $this->image->getImagesBlob();
693                         return $string;
694                 }
695
696                 $quality = false;
697
698                 ob_start();
699
700                 // Enable interlacing
701                 imageinterlace($this->image, true);
702
703                 switch ($this->getType()) {
704                         case "image/png":
705                                 $quality = Config::get('system', 'png_quality');
706                                 if ((!$quality) || ($quality > 9)) {
707                                         $quality = PNG_QUALITY;
708                                 }
709                                 imagepng($this->image, null, $quality);
710                                 break;
711                         case "image/jpeg":
712                                 $quality = Config::get('system', 'jpeg_quality');
713                                 if ((!$quality) || ($quality > 100)) {
714                                         $quality = JPEG_QUALITY;
715                                 }
716                                 imagejpeg($this->image, null, $quality);
717                 }
718                 $string = ob_get_contents();
719                 ob_end_clean();
720
721                 return $string;
722         }
723
724         /**
725          * Guess image mimetype from filename or from Content-Type header
726          *
727          * @param string  $filename Image filename
728          * @param boolean $fromcurl Check Content-Type header from curl request
729          * @param string  $header   passed headers to take into account
730          *
731          * @return object
732          * @throws \ImagickException
733          */
734         public static function guessType($filename, $fromcurl = false, $header = '')
735         {
736                 Logger::log('Image: guessType: '.$filename . ($fromcurl?' from curl headers':''), Logger::DEBUG);
737                 $type = null;
738                 if ($fromcurl) {
739                         $a = \get_app();
740                         $headers=[];
741                         $h = explode("\n", $header);
742                         foreach ($h as $l) {
743                                 $data = array_map("trim", explode(":", trim($l), 2));
744                                 if (count($data) > 1) {
745                                         list($k,$v) = $data;
746                                         $headers[$k] = $v;
747                                 }
748                         }
749                         if (array_key_exists('Content-Type', $headers))
750                                 $type = $headers['Content-Type'];
751                 }
752                 if (is_null($type)) {
753                         // Guessing from extension? Isn't that... dangerous?
754                         if (class_exists('Imagick') && file_exists($filename) && is_readable($filename)) {
755                                 /**
756                                  * Well, this not much better,
757                                  * but at least it comes from the data inside the image,
758                                  * we won't be tricked by a manipulated extension
759                                  */
760                                 $image = new Imagick($filename);
761                                 $type = $image->getImageMimeType();
762                                 $image->setInterlaceScheme(Imagick::INTERLACE_PLANE);
763                         } else {
764                                 $ext = pathinfo($filename, PATHINFO_EXTENSION);
765                                 $types = self::supportedTypes();
766                                 $type = "image/jpeg";
767                                 foreach ($types as $m => $e) {
768                                         if ($ext == $e) {
769                                                 $type = $m;
770                                         }
771                                 }
772                         }
773                 }
774                 Logger::log('Image: guessType: type='.$type, Logger::DEBUG);
775                 return $type;
776         }
777
778         /**
779          * @param string $url url
780          * @return object
781          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
782          */
783         public static function getInfoFromURL($url)
784         {
785                 $data = [];
786
787                 if (empty($url)) {
788                         return $data;
789                 }
790
791                 $data = Cache::get($url);
792
793                 if (is_null($data) || !$data || !is_array($data)) {
794                         $img_str = Network::fetchUrl($url, true, $redirects, 4);
795
796                         if (!$img_str) {
797                                 return false;
798                         }
799
800                         $filesize = strlen($img_str);
801
802                         try {
803                                 if (function_exists("getimagesizefromstring")) {
804                                         $data = @getimagesizefromstring($img_str);
805                                 } else {
806                                         $tempfile = tempnam(get_temppath(), "cache");
807
808                                         $a = \get_app();
809                                         $stamp1 = microtime(true);
810                                         file_put_contents($tempfile, $img_str);
811                                         $a->saveTimestamp($stamp1, "file");
812
813                                         $data = getimagesize($tempfile);
814                                         unlink($tempfile);
815                                 }
816                         } catch (Exception $e) {
817                                 return false;
818                         }
819
820                         if ($data) {
821                                 $data["size"] = $filesize;
822                         }
823
824                         Cache::set($url, $data);
825                 }
826
827                 return $data;
828         }
829
830         /**
831          * @param integer $width  width
832          * @param integer $height height
833          * @param integer $max    max
834          * @return array
835          */
836         public static function getScalingDimensions($width, $height, $max)
837         {
838                 $dest_width = $dest_height = 0;
839
840                 if ((!$width) || (!$height)) {
841                         return false;
842                 }
843
844                 if ($width > $max && $height > $max) {
845                         // very tall image (greater than 16:9)
846                         // constrain the width - let the height float.
847
848                         if ((($height * 9) / 16) > $width) {
849                                 $dest_width = $max;
850                                 $dest_height = intval(($height * $max) / $width);
851                         } elseif ($width > $height) {
852                                 // else constrain both dimensions
853                                 $dest_width = $max;
854                                 $dest_height = intval(($height * $max) / $width);
855                         } else {
856                                 $dest_width = intval(($width * $max) / $height);
857                                 $dest_height = $max;
858                         }
859                 } else {
860                         if ($width > $max) {
861                                 $dest_width = $max;
862                                 $dest_height = intval(($height * $max) / $width);
863                         } else {
864                                 if ($height > $max) {
865                                         // very tall image (greater than 16:9)
866                                         // but width is OK - don't do anything
867
868                                         if ((($height * 9) / 16) > $width) {
869                                                 $dest_width = $width;
870                                                 $dest_height = $height;
871                                         } else {
872                                                 $dest_width = intval(($width * $max) / $height);
873                                                 $dest_height = $max;
874                                         }
875                                 } else {
876                                         $dest_width = $width;
877                                         $dest_height = $height;
878                                 }
879                         }
880                 }
881                 return ["width" => $dest_width, "height" => $dest_height];
882         }
883
884         /**
885          * @brief This function is used by the fromgplus addon
886          * @param App     $a         App
887          * @param integer $uid       user id
888          * @param string  $imagedata optional, default empty
889          * @param string  $url       optional, default empty
890          * @return array
891          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
892          * @throws \ImagickException
893          */
894         public static function storePhoto(App $a, $uid, $imagedata = "", $url = "")
895         {
896                 $r = q(
897                         "SELECT `user`.`nickname`, `user`.`page-flags`, `contact`.`id` FROM `user` INNER JOIN `contact` on `user`.`uid` = `contact`.`uid`
898                         WHERE `user`.`uid` = %d AND `user`.`blocked` = 0 AND `contact`.`self` = 1 LIMIT 1",
899                         intval($uid)
900                 );
901
902                 if (!DBA::isResult($r)) {
903                         Logger::log("Can't detect user data for uid ".$uid, Logger::DEBUG);
904                         return([]);
905                 }
906
907                 $page_owner_nick  = $r[0]['nickname'];
908
909                 /// @TODO
910                 /// $default_cid      = $r[0]['id'];
911                 /// $community_page   = (($r[0]['page-flags'] == Contact::PAGE_COMMUNITY) ? true : false);
912
913                 if ((strlen($imagedata) == 0) && ($url == "")) {
914                         Logger::log("No image data and no url provided", Logger::DEBUG);
915                         return([]);
916                 } elseif (strlen($imagedata) == 0) {
917                         Logger::log("Uploading picture from ".$url, Logger::DEBUG);
918
919                         $stamp1 = microtime(true);
920                         $imagedata = @file_get_contents($url);
921                         $a->saveTimestamp($stamp1, "file");
922                 }
923
924                 $maximagesize = Config::get('system', 'maximagesize');
925
926                 if (($maximagesize) && (strlen($imagedata) > $maximagesize)) {
927                         Logger::log("Image exceeds size limit of ".$maximagesize, Logger::DEBUG);
928                         return([]);
929                 }
930
931                 $tempfile = tempnam(get_temppath(), "cache");
932
933                 $stamp1 = microtime(true);
934                 file_put_contents($tempfile, $imagedata);
935                 $a->saveTimestamp($stamp1, "file");
936
937                 $data = getimagesize($tempfile);
938
939                 if (!isset($data["mime"])) {
940                         unlink($tempfile);
941                         Logger::log("File is no picture", Logger::DEBUG);
942                         return([]);
943                 }
944
945                 $Image = new Image($imagedata, $data["mime"]);
946
947                 if (!$Image->isValid()) {
948                         unlink($tempfile);
949                         Logger::log("Picture is no valid picture", Logger::DEBUG);
950                         return([]);
951                 }
952
953                 $Image->orient($tempfile);
954                 unlink($tempfile);
955
956                 $max_length = Config::get('system', 'max_image_length');
957                 if (! $max_length) {
958                         $max_length = MAX_IMAGE_LENGTH;
959                 }
960
961                 if ($max_length > 0) {
962                         $Image->scaleDown($max_length);
963                 }
964
965                 $width = $Image->getWidth();
966                 $height = $Image->getHeight();
967
968                 $hash = Photo::newResource();
969
970                 $smallest = 0;
971
972                 // Pictures are always public by now
973                 //$defperm = '<'.$default_cid.'>';
974                 $defperm = "";
975                 $visitor = 0;
976
977                 $r = Photo::store($Image, $uid, $visitor, $hash, $tempfile, L10n::t('Wall Photos'), 0, 0, $defperm);
978
979                 if (!$r) {
980                         Logger::log("Picture couldn't be stored", Logger::DEBUG);
981                         return([]);
982                 }
983
984                 $image = ["page" => System::baseUrl().'/photos/'.$page_owner_nick.'/image/'.$hash,
985                         "full" => System::baseUrl()."/photo/{$hash}-0.".$Image->getExt()];
986
987                 if ($width > 800 || $height > 800) {
988                         $image["large"] = System::baseUrl()."/photo/{$hash}-0.".$Image->getExt();
989                 }
990
991                 if ($width > 640 || $height > 640) {
992                         $Image->scaleDown(640);
993                         $r = Photo::store($Image, $uid, $visitor, $hash, $tempfile, L10n::t('Wall Photos'), 1, 0, $defperm);
994                         if ($r) {
995                                 $image["medium"] = System::baseUrl()."/photo/{$hash}-1.".$Image->getExt();
996                         }
997                 }
998
999                 if ($width > 320 || $height > 320) {
1000                         $Image->scaleDown(320);
1001                         $r = Photo::store($Image, $uid, $visitor, $hash, $tempfile, L10n::t('Wall Photos'), 2, 0, $defperm);
1002                         if ($r) {
1003                                 $image["small"] = System::baseUrl()."/photo/{$hash}-2.".$Image->getExt();
1004                         }
1005                 }
1006
1007                 if ($width > 160 && $height > 160) {
1008                         $x = 0;
1009                         $y = 0;
1010
1011                         $min = $Image->getWidth();
1012                         if ($min > 160) {
1013                                 $x = ($min - 160) / 2;
1014                         }
1015
1016                         if ($Image->getHeight() < $min) {
1017                                 $min = $Image->getHeight();
1018                                 if ($min > 160) {
1019                                         $y = ($min - 160) / 2;
1020                                 }
1021                         }
1022
1023                         $min = 160;
1024                         $Image->crop(160, $x, $y, $min, $min);
1025
1026                         $r = Photo::store($Image, $uid, $visitor, $hash, $tempfile, L10n::t('Wall Photos'), 3, 0, $defperm);
1027                         if ($r) {
1028                                 $image["thumb"] = System::baseUrl()."/photo/{$hash}-3.".$Image->getExt();
1029                         }
1030                 }
1031
1032                 // Set the full image as preview image. This will be overwritten, if the picture is larger than 640.
1033                 $image["preview"] = $image["full"];
1034
1035                 // Deactivated, since that would result in a cropped preview, if the picture wasn't larger than 320
1036                 //if (isset($image["thumb"]))
1037                 //  $image["preview"] = $image["thumb"];
1038
1039                 // Unsure, if this should be activated or deactivated
1040                 //if (isset($image["small"]))
1041                 //  $image["preview"] = $image["small"];
1042
1043                 if (isset($image["medium"])) {
1044                         $image["preview"] = $image["medium"];
1045                 }
1046
1047                 return($image);
1048         }
1049 }