]> git.mxchange.org Git - friendica.git/blob - include/Photo.php
18e8e6e9a1316beba02b60f3ad19117ff291fadc
[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      * supported mimetypes and corresponding file extensions
11      */
12     static function supportedTypes() {
13         /**
14          * Imagick::queryFormats won't help us a lot there...
15          * At least, not yet, other parts of friendica uses this array
16          */
17         $t = array(
18             'image/jpeg' => 'jpg',
19             'image/png' => 'png',
20             'image/gif' => 'gif'
21         );
22         return $t;
23     }
24
25     public function __construct($data, $type=null) {
26         $this->image = new Imagick();
27         $this->image->readImageBlob($data);
28
29         // If it is a gif, it may be animated, get it ready for any future operations
30         if($this->image->getFormat() !== "GIF") $this->image = $this->image->coalesceImages();
31
32         $this->ext = strtolower($this->image->getImageFormat());
33     }
34
35     public function __destruct() {
36         if($this->image) {
37             $this->image->clear();
38             $this->image->destroy();
39         }
40     }
41
42     public function is_valid() {
43         return ($this->image !== FALSE);
44     }
45
46     public function getWidth() {
47         if(!$this->is_valid())
48             return FALSE;
49
50         return $this->image->getImageWidth();
51     }
52
53     public function getHeight() {
54         if(!$this->is_valid())
55             return FALSE;
56
57         return $this->image->getImageHeight();
58     }
59
60     public function getImage() {
61         if(!$this->is_valid())
62             return FALSE;
63
64         /* Clean it */
65         $this->image = $this->image->deconstructImages();
66         return $this->image;
67     }
68
69     public function getType() {
70         if(!$this->is_valid())
71             return FALSE;
72
73         // This should do the trick (see supportedTypes above)
74         return 'image/'. $this->getExt();
75     }
76
77     public function getExt() {
78         if(!$this->is_valid())
79             return FALSE;
80
81         return $this->ext;
82     }
83
84     public function scaleImage($max) {
85         if(!$this->is_valid())
86             return FALSE;
87
88         /**
89          * If it is not animated, there will be only one iteration here,
90          * so don't bother checking
91          */
92         // Don't forget to go back to the first frame for any further operation
93         $this->image->setFirstIterator();
94         do {
95             $this->image->resizeImage($max, $max, imagick::FILTER_LANCZOS, 1, true);
96         } while ($this->image->nextImage());
97     }
98
99     public function rotate($degrees) {
100         if(!$this->is_valid())
101             return FALSE;
102
103         $this->image->setFirstIterator();
104         do {
105             $this->image->rotateImage(new ImagickPixel(), $degrees);
106         } while ($this->image->nextImage());
107     }
108
109     public function flip($horiz = true, $vert = false) {
110         if(!$this->is_valid())
111             return FALSE;
112
113         $this->image->setFirstIterator();
114         do {
115             if($horiz) $this->image->flipImage();
116             if($vert) $this->image->flopImage();
117         } while ($this->image->nextImage());
118     }
119
120     public function orient($filename) {
121         // based off comment on http://php.net/manual/en/function.imagerotate.php
122
123         if(!$this->is_valid())
124             return FALSE;
125
126         if( (! function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg') )
127             return;
128
129         $exif = exif_read_data($filename);
130         $ort = $exif['Orientation'];
131
132         switch($ort)
133         {
134             case 1: // nothing
135                 break;
136
137             case 2: // horizontal flip
138                 $this->flip();
139                 break;
140
141             case 3: // 180 rotate left
142                 $this->rotate(180);
143                 break;
144
145             case 4: // vertical flip
146                 $this->flip(false, true);
147                 break;
148
149             case 5: // vertical flip + 90 rotate right
150                 $this->flip(false, true);
151                 $this->rotate(-90);
152                 break;
153
154             case 6: // 90 rotate right
155                 $this->rotate(-90);
156                 break;
157
158             case 7: // horizontal flip + 90 rotate right
159                 $this->flip();
160                 $this->rotate(-90);
161                 break;
162
163             case 8:    // 90 rotate left
164                 $this->rotate(90);
165                 break;
166         }
167     }
168
169
170
171     public function scaleImageUp($min) {
172         if(!$this->is_valid())
173             return FALSE;
174
175         $this->scaleImage($min);
176     }
177
178
179
180     public function scaleImageSquare($dim) {
181         if(!$this->is_valid())
182             return FALSE;
183
184         $this->image->setFirstIterator();
185         do {
186             $this->image->resizeImage($max, $max, imagick::FILTER_LANCZOS, 1, false);
187         } while ($this->image->nextImage());
188     }
189
190
191     public function cropImage($max,$x,$y,$w,$h) {
192         if(!$this->is_valid())
193             return FALSE;
194
195         $this->image->setFirstIterator();
196         do {
197             $this->image->cropImage($w, $h, $x, $y);
198             /**
199              * We need to remove the canva,
200              * or the image is not resized to the crop:
201              * http://php.net/manual/en/imagick.cropimage.php#97232
202              */
203             $this->image->setImagePage(0, 0, 0, 0);
204         } while ($this->image->nextImage());
205         $this->scaleImage($max);
206     }
207
208     public function saveImage($path) {
209         if(!$this->is_valid())
210             return FALSE;
211
212         $string = $this->imageString();
213         file_put_contents($path, $string);
214     }
215
216     public function imageString() {
217         if(!$this->is_valid())
218             return FALSE;
219
220         $quality = FALSE;
221
222         /**
223          * Hmmm,
224          * we should do the conversion/compression at the initialisation i think
225          * This method may be called several times,
226          * and there is no need to do that more than once
227          */
228         switch($this->image->getImageFormat()){
229             case "PNG":
230                 $quality = get_config('system','png_quality');
231                 if((! $quality) || ($quality > 9))
232                     $quality = PNG_QUALITY;
233                 /**
234                  * From http://www.imagemagick.org/script/command-line-options.php#quality:
235                  *
236                  * 'For the MNG and PNG image formats, the quality value sets
237                  * the zlib compression level (quality / 10) and filter-type (quality % 10).
238                  * The default PNG "quality" is 75, which means compression level 7 with adaptive PNG filtering,
239                  * unless the image has a color map, in which case it means compression level 7 with no PNG filtering'
240                  */
241                 $quality = $quality * 10;
242                 break;
243             case "GIF":
244                 // We change nothing here, do we?
245                 break;
246             default:
247                 // Convert to jpeg by default
248                 $quality = get_config('system','jpeg_quality');
249                 if((! $quality) || ($quality > 100))
250                     $quality = JPEG_QUALITY;
251                 $this->image->setImageFormat('jpeg');
252         }
253
254         if($quality !== FALSE) {
255             // Do we need to iterate for animations?
256             $this->image->setImageCompressionQuality($quality);
257             $this->image->stripImage();
258         }
259
260         $string = $this->image->getImagesBlob();
261         return $string;
262     }
263
264
265
266     public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '') {
267
268         $r = q("select `guid` from photo where `resource-id` = '%s' and `guid` != '' limit 1",
269             dbesc($rid)
270         );
271         if(count($r))
272             $guid = $r[0]['guid'];
273         else
274             $guid = get_guid();
275
276         $x = q("select id from photo where `resource-id` = '%s' and uid = %d and `contact-id` = %d and `scale` = %d limit 1",
277                 dbesc($rid),
278                 intval($uid),
279                 intval($cid),
280                 intval($scale)
281         );
282         if(count($x)) {
283             $r = q("UPDATE `photo`
284                 set `uid` = %d,
285                 `contact-id` = %d,
286                 `guid` = '%s',
287                 `resource-id` = '%s',
288                 `created` = '%s',
289                 `edited` = '%s',
290                 `filename` = '%s',
291                 `type` = '%s',
292                 `album` = '%s',
293                 `height` = %d,
294                 `width` = %d,
295                 `data` = '%s',
296                 `scale` = %d,
297                 `profile` = %d,
298                 `allow_cid` = '%s',
299                 `allow_gid` = '%s',
300                 `deny_cid` = '%s',
301                 `deny_gid` = '%s'
302                 where id = %d limit 1",
303
304                 intval($uid),
305                 intval($cid),
306                 dbesc($guid),
307                 dbesc($rid),
308                 dbesc(datetime_convert()),
309                 dbesc(datetime_convert()),
310                 dbesc(basename($filename)),
311                 dbesc($this->getType()),
312                 dbesc($album),
313                 intval($this->getHeight()),
314                 intval($this->getWidth()),
315                 dbesc($this->imageString()),
316                 intval($scale),
317                 intval($profile),
318                 dbesc($allow_cid),
319                 dbesc($allow_gid),
320                 dbesc($deny_cid),
321                 dbesc($deny_gid),
322                 intval($x[0]['id'])
323             );
324         }
325         else {
326             $r = q("INSERT INTO `photo`
327                 ( `uid`, `contact-id`, `guid`, `resource-id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `data`, `scale`, `profile`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid` )
328                 VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', %d, %d, '%s', '%s', '%s', '%s' )",
329                 intval($uid),
330                 intval($cid),
331                 dbesc($guid),
332                 dbesc($rid),
333                 dbesc(datetime_convert()),
334                 dbesc(datetime_convert()),
335                 dbesc(basename($filename)),
336                 dbesc($this->getType()),
337                 dbesc($album),
338                 intval($this->getHeight()),
339                 intval($this->getWidth()),
340                 dbesc($this->imageString()),
341                 intval($scale),
342                 intval($profile),
343                 dbesc($allow_cid),
344                 dbesc($allow_gid),
345                 dbesc($deny_cid),
346                 dbesc($deny_gid)
347             );
348         }
349         return $r;
350     }
351 }}
352
353
354 /**
355  * Guess image mimetype from filename or from Content-Type header
356  *
357  * @arg $filename string Image filename
358  * @arg $fromcurl boolean Check Content-Type header from curl request
359  */
360 function guess_image_type($filename, $fromcurl=false) {
361     logger('Photo: guess_image_type: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG);
362     $type = null;
363     if ($fromcurl) {
364         $a = get_app();
365         $headers=array();
366         $h = explode("\n",$a->get_curl_headers());
367         foreach ($h as $l) {
368             list($k,$v) = array_map("trim", explode(":", trim($l), 2));
369             $headers[$k] = $v;
370         }
371         if (array_key_exists('Content-Type', $headers))
372             $type = $headers['Content-Type'];
373     }
374     if (is_null($type)){
375         // Guessing from extension? Isn't that... dangerous?
376         /*$ext = pathinfo($filename, PATHINFO_EXTENSION);
377         $types = Photo::supportedTypes();
378         $type = "image/jpeg";
379         foreach ($types as $m=>$e){
380             if ($ext==$e) $type = $m;
381         }*/
382         /**
383          * Well, this not much better,
384          * but at least it comes from the data inside the image,
385          * we won't be tricked by a manipulated extension
386          */
387         $image = new Imagick($filename);
388         $type = 'image/'. strtolower($image->getImageFormat());
389     }
390     logger('Photo: guess_image_type: type='.$type, LOGGER_DEBUG);
391     return $type;
392
393 }
394
395 function import_profile_photo($photo,$uid,$cid) {
396
397     $a = get_app();
398
399     $r = q("select `resource-id` from photo where `uid` = %d and `contact-id` = %d and `scale` = 4 and `album` = 'Contact Photos' limit 1",
400         intval($uid),
401         intval($cid)
402     );
403     if(count($r)) {
404         $hash = $r[0]['resource-id'];
405     }
406     else {
407         $hash = photo_new_resource();
408     }
409
410     $photo_failure = false;
411
412     $filename = basename($photo);
413     $img_str = fetch_url($photo,true);
414
415     $img = new Photo($img_str);
416     if($img->is_valid()) {
417
418         $img->scaleImageSquare(175);
419
420         $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4 );
421
422         if($r === false)
423             $photo_failure = true;
424
425         $img->scaleImage(80);
426
427         $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5 );
428
429         if($r === false)
430             $photo_failure = true;
431
432         $img->scaleImage(48);
433
434         $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6 );
435
436         if($r === false)
437             $photo_failure = true;
438
439         $photo = $a->get_baseurl() . '/photo/' . $hash . '-4.' . $img->getExt();
440         $thumb = $a->get_baseurl() . '/photo/' . $hash . '-5.' . $img->getExt();
441         $micro = $a->get_baseurl() . '/photo/' . $hash . '-6.' . $img->getExt();
442     }
443     else
444         $photo_failure = true;
445
446     if($photo_failure) {
447         $photo = $a->get_baseurl() . '/images/person-175.jpg';
448         $thumb = $a->get_baseurl() . '/images/person-80.jpg';
449         $micro = $a->get_baseurl() . '/images/person-48.jpg';
450     }
451
452     return(array($photo,$thumb,$micro));
453
454 }