]> git.mxchange.org Git - friendica.git/blob - include/Photo.php
4ac84d90158ad73e33e5ddf112c480e575941297
[friendica.git] / include / Photo.php
1 <?php
2
3 if(! class_exists("Photo")) {
4 class Photo {
5
6     private $image;
7
8     /**
9      * Put back gd stuff, not everybody have Imagick
10      */
11     private $imagick;
12     private $width;
13     private $height;
14     private $valid;
15     private $type;
16     private $types;
17
18     /**
19      * supported mimetypes and corresponding file extensions
20      */
21     static function supportedTypes() {
22         if(class_exists('Imagick')) {
23             /**
24              * Imagick::queryFormats won't help us a lot there...
25              * At least, not yet, other parts of friendica uses this array
26              */
27             $t = array(
28                 'image/jpeg' => 'jpg',
29                 'image/png' => 'png',
30                 'image/gif' => 'gif'
31             );
32         } else {
33             $t = array();
34             $t['image/jpeg'] ='jpg';
35             if (imagetypes() & IMG_PNG) $t['image/png'] = 'png';
36         }
37
38         return $t;
39     }
40
41     public function __construct($data, $type=null) {
42         $this->imagick = class_exists('Imagick');
43         $this->types = $this->supportedTypes();
44
45         if($this->is_imagick()) {
46             $this->image = new Imagick();
47             $this->image->readImageBlob($data);
48
49             /**
50              * Setup the image to the format of the one we just loaded,
51              * we'll change it to something else if we need to at the time we save it
52              */
53             $this->image->setFormat($this->image->getImageFormat());
54
55             // If it is a gif, it may be animated, get it ready for any future operations
56             if($this->image->getFormat() !== "GIF") $this->image = $this->image->coalesceImages();
57         } else {
58             if (!array_key_exists($type,$this->types)){
59                 $type='image/jpeg';
60             }
61             $this->valid = false;
62             $this->type = $type;
63             $this->image = @imagecreatefromstring($data);
64             if($this->image !== FALSE) {
65                 $this->width  = imagesx($this->image);
66                 $this->height = imagesy($this->image);
67                 $this->valid  = true;
68                 imagealphablending($this->image, false);
69                 imagesavealpha($this->image, true);
70             }
71         }
72     }
73
74     public function __destruct() {
75         if($this->image) {
76             if($this->is_imagick()) {
77                 $this->image->clear();
78                 $this->image->destroy();
79                 return;
80             }
81             imagedestroy($this->image);
82         }
83     }
84
85     public function is_imagick() {
86         return $this->imagick;
87     }
88
89     public function is_valid() {
90         if($this->is_imagick())
91             return ($this->image !== FALSE);
92         return $this->valid;
93     }
94
95     public function getWidth() {
96         if(!$this->is_valid())
97             return FALSE;
98
99         if($this->is_imagick())
100             return $this->image->getImageWidth();
101         return $this->width;
102     }
103
104     public function getHeight() {
105         if(!$this->is_valid())
106             return FALSE;
107
108         if($this->is_imagick())
109             return $this->image->getImageHeight();
110         return $this->height;
111     }
112
113     public function getImage() {
114         if(!$this->is_valid())
115             return FALSE;
116
117         if($this->is_imagick()) {
118             /* Clean it */
119             $this->image = $this->image->deconstructImages();
120             return $this->image;
121         }
122         return $this->image;
123     }
124
125     public function getType() {
126         if(!$this->is_valid())
127             return FALSE;
128
129         if($this->is_imagick()) {
130             return $this->image->getImageMimeType();
131         }
132         return $this->type;
133     }
134
135     public function getExt() {
136         if(!$this->is_valid())
137             return FALSE;
138
139         return $this->types[$this->getType()];
140     }
141
142     public function scaleImage($max) {
143         if(!$this->is_valid())
144             return FALSE;
145
146         if($this->is_imagick()) {
147             /**
148              * If it is not animated, there will be only one iteration here,
149              * so don't bother checking
150              */
151             // Don't forget to go back to the first frame
152             $this->image->setFirstIterator();
153             do {
154                 $this->image->resizeImage($max, $max, imagick::FILTER_LANCZOS, 1, true);
155             } while ($this->image->nextImage());
156             return;
157         }
158
159         $width = $this->width;
160         $height = $this->height;
161
162         $dest_width = $dest_height = 0;
163
164         if((! $width)|| (! $height))
165             return FALSE;
166
167         if($width > $max && $height > $max) {
168             if($width > $height) {
169                 $dest_width = $max;
170                 $dest_height = intval(( $height * $max ) / $width);
171             }
172             else {
173                 $dest_width = intval(( $width * $max ) / $height);
174                 $dest_height = $max;
175             }
176         }
177         else {
178             if( $width > $max ) {
179                 $dest_width = $max;
180                 $dest_height = intval(( $height * $max ) / $width);
181             }
182             else {
183                 if( $height > $max ) {
184                     $dest_width = intval(( $width * $max ) / $height);
185                     $dest_height = $max;
186                 }
187                 else {
188                     $dest_width = $width;
189                     $dest_height = $height;
190                 }
191             }
192         }
193
194
195         $dest = imagecreatetruecolor( $dest_width, $dest_height );
196         imagealphablending($dest, false);
197         imagesavealpha($dest, true);
198         if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha
199         imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height);
200         if($this->image)
201             imagedestroy($this->image);
202         $this->image = $dest;
203         $this->width  = imagesx($this->image);
204         $this->height = imagesy($this->image);
205     }
206
207     public function rotate($degrees) {
208         if(!$this->is_valid())
209             return FALSE;
210
211         if($this->is_imagick()) {
212             $this->image->setFirstIterator();
213             do {
214                 $this->image->rotateImage(new ImagickPixel(), $degrees);
215             } while ($this->image->nextImage());
216             return;
217         }
218
219         $this->image  = imagerotate($this->image,$degrees,0);
220         $this->width  = imagesx($this->image);
221         $this->height = imagesy($this->image);
222     }
223
224     public function flip($horiz = true, $vert = false) {
225         if(!$this->is_valid())
226             return FALSE;
227
228         if($this->is_imagick()) {
229             $this->image->setFirstIterator();
230             do {
231                 if($horiz) $this->image->flipImage();
232                 if($vert) $this->image->flopImage();
233             } while ($this->image->nextImage());
234             return;
235         }
236
237         $w = imagesx($this->image);
238         $h = imagesy($this->image);
239         $flipped = imagecreate($w, $h);
240         if($horiz) {
241             for ($x = 0; $x < $w; $x++) {
242                 imagecopy($flipped, $this->image, $x, 0, $w - $x - 1, 0, 1, $h);
243             }
244         }
245         if($vert) {
246             for ($y = 0; $y < $h; $y++) {
247                 imagecopy($flipped, $this->image, 0, $y, 0, $h - $y - 1, $w, 1);
248             }
249         }
250         $this->image = $flipped;
251     }
252
253     public function orient($filename) {
254         // based off comment on http://php.net/manual/en/function.imagerotate.php
255
256         if(!$this->is_valid())
257             return FALSE;
258
259         if( (! function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg') )
260             return;
261
262         $exif = exif_read_data($filename);
263         $ort = $exif['Orientation'];
264
265         switch($ort)
266         {
267             case 1: // nothing
268                 break;
269
270             case 2: // horizontal flip
271                 $this->flip();
272                 break;
273
274             case 3: // 180 rotate left
275                 $this->rotate(180);
276                 break;
277
278             case 4: // vertical flip
279                 $this->flip(false, true);
280                 break;
281
282             case 5: // vertical flip + 90 rotate right
283                 $this->flip(false, true);
284                 $this->rotate(-90);
285                 break;
286
287             case 6: // 90 rotate right
288                 $this->rotate(-90);
289                 break;
290
291             case 7: // horizontal flip + 90 rotate right
292                 $this->flip();
293                 $this->rotate(-90);
294                 break;
295
296             case 8:    // 90 rotate left
297                 $this->rotate(90);
298                 break;
299         }
300     }
301
302
303
304     public function scaleImageUp($min) {
305         if(!$this->is_valid())
306             return FALSE;
307
308         if($this->is_imagick())
309             return $this->scaleImage($min);
310
311         $width = $this->width;
312         $height = $this->height;
313
314         $dest_width = $dest_height = 0;
315
316         if((! $width)|| (! $height))
317             return FALSE;
318
319         if($width < $min && $height < $min) {
320             if($width > $height) {
321                 $dest_width = $min;
322                 $dest_height = intval(( $height * $min ) / $width);
323             }
324             else {
325                 $dest_width = intval(( $width * $min ) / $height);
326                 $dest_height = $min;
327             }
328         }
329         else {
330             if( $width < $min ) {
331                 $dest_width = $min;
332                 $dest_height = intval(( $height * $min ) / $width);
333             }
334             else {
335                 if( $height < $min ) {
336                     $dest_width = intval(( $width * $min ) / $height);
337                     $dest_height = $min;
338                 }
339                 else {
340                     $dest_width = $width;
341                     $dest_height = $height;
342                 }
343             }
344         }
345
346
347         $dest = imagecreatetruecolor( $dest_width, $dest_height );
348         imagealphablending($dest, false);
349         imagesavealpha($dest, true);
350         if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha
351         imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height);
352         if($this->image)
353             imagedestroy($this->image);
354         $this->image = $dest;
355         $this->width  = imagesx($this->image);
356         $this->height = imagesy($this->image);
357     }
358
359
360
361     public function scaleImageSquare($dim) {
362         if(!$this->is_valid())
363             return FALSE;
364
365         if($this->is_imagick()) {
366             $this->image->setFirstIterator();
367             do {
368                 $this->image->resizeImage($dim, $dim, imagick::FILTER_LANCZOS, 1, false);
369             } while ($this->image->nextImage());
370             return;
371         }
372
373         $dest = imagecreatetruecolor( $dim, $dim );
374         imagealphablending($dest, false);
375         imagesavealpha($dest, true);
376         if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha
377         imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dim, $dim, $this->width, $this->height);
378         if($this->image)
379             imagedestroy($this->image);
380         $this->image = $dest;
381         $this->width  = imagesx($this->image);
382         $this->height = imagesy($this->image);
383     }
384
385
386     public function cropImage($max,$x,$y,$w,$h) {
387         if(!$this->is_valid())
388             return FALSE;
389
390         if($this->is_imagick()) {
391             $this->image->setFirstIterator();
392             do {
393                 $this->image->cropImage($w, $h, $x, $y);
394                 /**
395                  * We need to remove the canva,
396                  * or the image is not resized to the crop:
397                  * http://php.net/manual/en/imagick.cropimage.php#97232
398                  */
399                 $this->image->setImagePage(0, 0, 0, 0);
400             } while ($this->image->nextImage());
401             return $this->scaleImage($max);
402         }
403
404         $dest = imagecreatetruecolor( $max, $max );
405         imagealphablending($dest, false);
406         imagesavealpha($dest, true);
407         if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha
408         imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h);
409         if($this->image)
410             imagedestroy($this->image);
411         $this->image = $dest;
412         $this->width  = imagesx($this->image);
413         $this->height = imagesy($this->image);
414     }
415
416     public function saveImage($path) {
417         if(!$this->is_valid())
418             return FALSE;
419
420         $string = $this->imageString();
421         file_put_contents($path, $string);
422     }
423
424     public function imageString() {
425         if(!$this->is_valid())
426             return FALSE;
427
428         $quality = FALSE;
429
430         /**
431          * Hmmm, for Imagick
432          * we should do the conversion/compression at the initialisation i think
433          * This method may be called several times,
434          * and there is no need to do that more than once
435          */
436
437         if(!$this->is_imagick()) ob_start();
438
439         switch($this->getType()){
440             case "image/png":
441                 $quality = get_config('system','png_quality');
442                 if((! $quality) || ($quality > 9))
443                     $quality = PNG_QUALITY;
444                 if($this->is_imagick()) {
445                     /**
446                      * From http://www.imagemagick.org/script/command-line-options.php#quality:
447                      *
448                      * 'For the MNG and PNG image formats, the quality value sets
449                      * the zlib compression level (quality / 10) and filter-type (quality % 10).
450                      * The default PNG "quality" is 75, which means compression level 7 with adaptive PNG filtering,
451                      * unless the image has a color map, in which case it means compression level 7 with no PNG filtering'
452                      */
453                     $quality = $quality * 10;
454                 } else imagepng($this->image,NULL, $quality);
455                 break;
456             case "image/gif":
457                 // We change nothing here, do we?
458                 break;
459             default:
460                 // Convert to jpeg by default
461                 $quality = get_config('system','jpeg_quality');
462                 if((! $quality) || ($quality > 100))
463                     $quality = JPEG_QUALITY;
464                 if($this->is_imagick()) {
465                     $this->image->setFormat('jpeg');
466                     logger('Photo: imageString: Unhandled mime type ('. $this->getType() .'), Imagick format is "'. $this->image->getFormat() .'"', LOGGER_DEBUG);
467                 }
468                 else imagejpeg($this->image,NULL,$quality);
469         }
470
471         if($this->is_imagick()) {
472             if($quality !== FALSE) {
473                 // Do we need to iterate for animations?
474                 $this->image->setCompressionQuality($quality);
475                 $this->image->stripImage();
476             }
477
478             $string = $this->image->getImagesBlob();
479         } else {
480             $string = ob_get_contents();
481             ob_end_clean();
482         }
483
484         return $string;
485     }
486
487
488
489     public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '') {
490
491         $r = q("select `guid` from photo where `resource-id` = '%s' and `guid` != '' limit 1",
492             dbesc($rid)
493         );
494         if(count($r))
495             $guid = $r[0]['guid'];
496         else
497             $guid = get_guid();
498
499         $x = q("select id from photo where `resource-id` = '%s' and uid = %d and `contact-id` = %d and `scale` = %d limit 1",
500                 dbesc($rid),
501                 intval($uid),
502                 intval($cid),
503                 intval($scale)
504         );
505         if(count($x)) {
506             $r = q("UPDATE `photo`
507                 set `uid` = %d,
508                 `contact-id` = %d,
509                 `guid` = '%s',
510                 `resource-id` = '%s',
511                 `created` = '%s',
512                 `edited` = '%s',
513                 `filename` = '%s',
514                 `type` = '%s',
515                 `album` = '%s',
516                 `height` = %d,
517                 `width` = %d,
518                 `data` = '%s',
519                 `scale` = %d,
520                 `profile` = %d,
521                 `allow_cid` = '%s',
522                 `allow_gid` = '%s',
523                 `deny_cid` = '%s',
524                 `deny_gid` = '%s'
525                 where id = %d limit 1",
526
527                 intval($uid),
528                 intval($cid),
529                 dbesc($guid),
530                 dbesc($rid),
531                 dbesc(datetime_convert()),
532                 dbesc(datetime_convert()),
533                 dbesc(basename($filename)),
534                 dbesc($this->getType()),
535                 dbesc($album),
536                 intval($this->getHeight()),
537                 intval($this->getWidth()),
538                 dbesc($this->imageString()),
539                 intval($scale),
540                 intval($profile),
541                 dbesc($allow_cid),
542                 dbesc($allow_gid),
543                 dbesc($deny_cid),
544                 dbesc($deny_gid),
545                 intval($x[0]['id'])
546             );
547         }
548         else {
549             $r = q("INSERT INTO `photo`
550                 ( `uid`, `contact-id`, `guid`, `resource-id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `data`, `scale`, `profile`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid` )
551                 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', %d, %d, '%s', '%s', '%s', '%s' )",
552                 intval($uid),
553                 intval($cid),
554                 dbesc($guid),
555                 dbesc($rid),
556                 dbesc(datetime_convert()),
557                 dbesc(datetime_convert()),
558                 dbesc(basename($filename)),
559                 dbesc($this->getType()),
560                 dbesc($album),
561                 intval($this->getHeight()),
562                 intval($this->getWidth()),
563                 dbesc($this->imageString()),
564                 intval($scale),
565                 intval($profile),
566                 dbesc($allow_cid),
567                 dbesc($allow_gid),
568                 dbesc($deny_cid),
569                 dbesc($deny_gid)
570             );
571         }
572         return $r;
573     }
574 }}
575
576
577 /**
578  * Guess image mimetype from filename or from Content-Type header
579  *
580  * @arg $filename string Image filename
581  * @arg $fromcurl boolean Check Content-Type header from curl request
582  */
583 function guess_image_type($filename, $fromcurl=false) {
584     logger('Photo: guess_image_type: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG);
585     $type = null;
586     if ($fromcurl) {
587         $a = get_app();
588         $headers=array();
589         $h = explode("\n",$a->get_curl_headers());
590         foreach ($h as $l) {
591             list($k,$v) = array_map("trim", explode(":", trim($l), 2));
592             $headers[$k] = $v;
593         }
594         if (array_key_exists('Content-Type', $headers))
595             $type = $headers['Content-Type'];
596     }
597     if (is_null($type)){
598         // Guessing from extension? Isn't that... dangerous?
599         if($this->is_imagick()) {
600             /**
601              * Well, this not much better,
602              * but at least it comes from the data inside the image,
603              * we won't be tricked by a manipulated extension
604              */
605             $image = new Imagick($filename);
606             $type = $image->getImageMimeType();
607         } else {
608             $ext = pathinfo($filename, PATHINFO_EXTENSION);
609             $types = Photo::supportedTypes();
610             $type = "image/jpeg";
611             foreach ($types as $m=>$e){
612                 if ($ext==$e) $type = $m;
613             }
614         }
615     }
616     logger('Photo: guess_image_type: type='.$type, LOGGER_DEBUG);
617     return $type;
618
619 }
620
621 function import_profile_photo($photo,$uid,$cid) {
622
623     $a = get_app();
624
625     $r = q("select `resource-id` from photo where `uid` = %d and `contact-id` = %d and `scale` = 4 and `album` = 'Contact Photos' limit 1",
626         intval($uid),
627         intval($cid)
628     );
629     if(count($r)) {
630         $hash = $r[0]['resource-id'];
631     }
632     else {
633         $hash = photo_new_resource();
634     }
635
636     $photo_failure = false;
637
638     $filename = basename($photo);
639     $img_str = fetch_url($photo,true);
640
641     if($this->is_imagick()) $type = null;
642     else {
643         // guess mimetype from headers or filename
644         $type = guess_image_type($photo,true);
645     }
646     $img = new Photo($img_str, $type);
647     if($img->is_valid()) {
648
649         $img->scaleImageSquare(175);
650
651         $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4 );
652
653         if($r === false)
654             $photo_failure = true;
655
656         $img->scaleImage(80);
657
658         $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5 );
659
660         if($r === false)
661             $photo_failure = true;
662
663         $img->scaleImage(48);
664
665         $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6 );
666
667         if($r === false)
668             $photo_failure = true;
669
670         $photo = $a->get_baseurl() . '/photo/' . $hash . '-4.' . $img->getExt();
671         $thumb = $a->get_baseurl() . '/photo/' . $hash . '-5.' . $img->getExt();
672         $micro = $a->get_baseurl() . '/photo/' . $hash . '-6.' . $img->getExt();
673     }
674     else
675         $photo_failure = true;
676
677     if($photo_failure) {
678         $photo = $a->get_baseurl() . '/images/person-175.jpg';
679         $thumb = $a->get_baseurl() . '/images/person-80.jpg';
680         $micro = $a->get_baseurl() . '/images/person-48.jpg';
681     }
682
683     return(array($photo,$thumb,$micro));
684
685 }