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