3 if(! class_exists("Photo")) {
10 * supported mimetypes and corresponding file extensions
12 static function supportedTypes() {
14 * Imagick::queryFormats won't help us a lot there...
15 * At least, not yet, other parts of friendica uses this array
18 'image/jpeg' => 'jpg',
25 public function __construct($data, $type=null) {
26 $this->image = new Imagick();
27 $this->image->readImageBlob($data);
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();
32 $this->ext = strtolower($this->image->getImageFormat());
35 public function __destruct() {
37 $this->image->clear();
38 $this->image->destroy();
42 public function is_valid() {
43 return ($this->image !== FALSE);
46 public function getWidth() {
47 if(!$this->is_valid())
50 return $this->image->getImageWidth();
53 public function getHeight() {
54 if(!$this->is_valid())
57 return $this->image->getImageHeight();
60 public function getImage() {
61 if(!$this->is_valid())
65 $this->image = $this->image->deconstructImages();
69 public function getType() {
70 if(!$this->is_valid())
73 // This should do the trick (see supportedTypes above)
74 return 'image/'. $this->getExt();
77 public function getExt() {
78 if(!$this->is_valid())
84 public function scaleImage($max) {
85 if(!$this->is_valid())
89 * If it is not animated, there will be only one iteration here,
90 * so don't bother checking
92 // Don't forget to go back to the first frame for any further operation
93 $this->image->setFirstIterator();
95 $this->image->resizeImage($max, $max, imagick::FILTER_LANCZOS, 1, true);
96 } while ($this->image->nextImage());
99 public function rotate($degrees) {
100 if(!$this->is_valid())
103 $this->image->setFirstIterator();
105 $this->image->rotateImage(new ImagickPixel(), $degrees);
106 } while ($this->image->nextImage());
109 public function flip($horiz = true, $vert = false) {
110 if(!$this->is_valid())
113 $this->image->setFirstIterator();
115 if($horiz) $this->image->flipImage();
116 if($vert) $this->image->flopImage();
117 } while ($this->image->nextImage());
120 public function orient($filename) {
121 // based off comment on http://php.net/manual/en/function.imagerotate.php
123 if(!$this->is_valid())
126 if( (! function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg') )
129 $exif = exif_read_data($filename);
130 $ort = $exif['Orientation'];
137 case 2: // horizontal flip
141 case 3: // 180 rotate left
145 case 4: // vertical flip
146 $this->flip(false, true);
149 case 5: // vertical flip + 90 rotate right
150 $this->flip(false, true);
154 case 6: // 90 rotate right
158 case 7: // horizontal flip + 90 rotate right
163 case 8: // 90 rotate left
171 public function scaleImageUp($min) {
172 if(!$this->is_valid())
175 $this->scaleImage($min);
180 public function scaleImageSquare($dim) {
181 if(!$this->is_valid())
184 $this->image->setFirstIterator();
186 $this->image->resizeImage($max, $max, imagick::FILTER_LANCZOS, 1, false);
187 } while ($this->image->nextImage());
191 public function cropImage($max,$x,$y,$w,$h) {
192 if(!$this->is_valid())
195 $this->image->setFirstIterator();
197 $this->image->cropImage($w, $h, $x, $y);
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
203 $this->image->setImagePage(0, 0, 0, 0);
204 } while ($this->image->nextImage());
205 $this->scaleImage($max);
208 public function saveImage($path) {
209 if(!$this->is_valid())
212 $string = $this->imageString();
213 file_put_contents($path, $string);
216 public function imageString() {
217 if(!$this->is_valid())
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
228 switch($this->image->getImageFormat()){
230 $quality = get_config('system','png_quality');
231 if((! $quality) || ($quality > 9))
232 $quality = PNG_QUALITY;
234 * From http://www.imagemagick.org/script/command-line-options.php#quality:
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'
241 $quality = $quality * 10;
244 // We change nothing here, do we?
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');
254 if($quality !== FALSE) {
255 // Do we need to iterate for animations?
256 $this->image->setImageCompressionQuality($quality);
257 $this->image->stripImage();
260 $string = $this->image->getImagesBlob();
266 public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '') {
268 $r = q("select `guid` from photo where `resource-id` = '%s' and `guid` != '' limit 1",
272 $guid = $r[0]['guid'];
276 $x = q("select id from photo where `resource-id` = '%s' and uid = %d and `contact-id` = %d and `scale` = %d limit 1",
283 $r = q("UPDATE `photo`
287 `resource-id` = '%s',
302 where id = %d limit 1",
308 dbesc(datetime_convert()),
309 dbesc(datetime_convert()),
310 dbesc(basename($filename)),
311 dbesc($this->getType()),
313 intval($this->getHeight()),
314 intval($this->getWidth()),
315 dbesc($this->imageString()),
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' )",
333 dbesc(datetime_convert()),
334 dbesc(datetime_convert()),
335 dbesc(basename($filename)),
336 dbesc($this->getType()),
338 intval($this->getHeight()),
339 intval($this->getWidth()),
340 dbesc($this->imageString()),
355 * Guess image mimetype from filename or from Content-Type header
357 * @arg $filename string Image filename
358 * @arg $fromcurl boolean Check Content-Type header from curl request
360 function guess_image_type($filename, $fromcurl=false) {
361 logger('Photo: guess_image_type: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG);
366 $h = explode("\n",$a->get_curl_headers());
368 list($k,$v) = array_map("trim", explode(":", trim($l), 2));
371 if (array_key_exists('Content-Type', $headers))
372 $type = $headers['Content-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;
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
387 $image = new Imagick($filename);
388 $type = 'image/'. strtolower($image->getImageFormat());
390 logger('Photo: guess_image_type: type='.$type, LOGGER_DEBUG);
395 function import_profile_photo($photo,$uid,$cid) {
399 $r = q("select `resource-id` from photo where `uid` = %d and `contact-id` = %d and `scale` = 4 and `album` = 'Contact Photos' limit 1",
404 $hash = $r[0]['resource-id'];
407 $hash = photo_new_resource();
410 $photo_failure = false;
412 $filename = basename($photo);
413 $img_str = fetch_url($photo,true);
415 $img = new Photo($img_str);
416 if($img->is_valid()) {
418 $img->scaleImageSquare(175);
420 $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4 );
423 $photo_failure = true;
425 $img->scaleImage(80);
427 $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5 );
430 $photo_failure = true;
432 $img->scaleImage(48);
434 $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6 );
437 $photo_failure = true;
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();
444 $photo_failure = true;
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';
452 return(array($photo,$thumb,$micro));