From d70bba28065fb3105543ef40d91f6db839d35e0f Mon Sep 17 00:00:00 2001 From: Klaus Weidenbach Date: Fri, 1 Feb 2019 00:21:01 +0100 Subject: Clean up and documentation in photo_drive. --- include/photo/photo_driver.php | 513 ++++++++++++++++++++++------------------- 1 file changed, 271 insertions(+), 242 deletions(-) diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php index 5c8ed9bdc..718bf875b 100644 --- a/include/photo/photo_driver.php +++ b/include/photo/photo_driver.php @@ -1,18 +1,30 @@ -= 0) { + if(version_compare($m[1], '6.6.7') >= 0) { require_once('include/photo/photo_imagick.php'); - $ph = new photo_imagick($data,$type); - } - else { + $ph = new photo_imagick($data, $type); + } else { // earlier imagick versions have issues with scaling png's // don't log this because it will just fill the logfile. - // leave this note here so those who are looking for why + // leave this note here so those who are looking for why // we aren't using imagick can find it } } if(! $ph) { require_once('include/photo/photo_gd.php'); - $ph = new photo_gd($data,$type); + $ph = new photo_gd($data, $type); } return $ph; } - - +/** + * @brief Abstract photo driver class. + * + */ abstract class photo_driver { protected $image; @@ -79,12 +92,12 @@ abstract class photo_driver { public function __construct($data, $type='') { $this->types = $this->supportedTypes(); - if (! array_key_exists($type,$this->types)){ - $type='image/jpeg'; + if (! array_key_exists($type, $this->types)){ + $type = 'image/jpeg'; } $this->type = $type; $this->valid = false; - $this->load($data,$type); + $this->load($data, $type); } public function __destruct() { @@ -99,12 +112,14 @@ abstract class photo_driver { public function getWidth() { if(!$this->is_valid()) return FALSE; + return $this->width; } public function getHeight() { if(!$this->is_valid()) return FALSE; + return $this->height; } @@ -112,6 +127,7 @@ abstract class photo_driver { public function saveImage($path) { if(!$this->is_valid()) return FALSE; + return (file_put_contents($path, $this->imageString()) ? true : false); } @@ -131,12 +147,14 @@ abstract class photo_driver { } /** - * @brief scale image - * int $max maximum pixel size in either dimension - * boolean $float_height - if true allow height to float to any length on tall images, - * constraining only the width + * @brief Scale image to max pixel size in either dimension. + * + * @param int $max maximum pixel size in either dimension + * @param boolean $float_height (optional) + * if true allow height to float to any length on tall images, constraining + * only the width + * @return boolean|void false on failure, otherwise void */ - public function scaleImage($max, $float_height = true) { if(!$this->is_valid()) return FALSE; @@ -203,7 +221,6 @@ abstract class photo_driver { if(!$this->is_valid()) return FALSE; - $width = $this->width; $height = $this->height; @@ -241,33 +258,39 @@ abstract class photo_driver { $this->doScaleImage($dest_width,$dest_height); } - + /** + * @brief Scales image to a square. + * + * @param int $dim Pixel of squre image + * @return boolean|void false on failure, otherwise void + */ public function scaleImageSquare($dim) { if(!$this->is_valid()) return FALSE; - $this->doScaleImage($dim,$dim); - } + $this->doScaleImage($dim, $dim); + } /** - * @brief reads exif data from filename + * @brief reads exif data from filename. + * + * @param string $filename + * @return boolean|array */ - public function exif($filename) { - - if((! function_exists('exif_read_data')) + if((! function_exists('exif_read_data')) || (! in_array($this->getType(), [ 'image/jpeg' , 'image/tiff'] ))) { return false; } /* * PHP 7.2 allows you to use a stream resource, which should reduce/avoid - * memory exhaustion on large images. + * memory exhaustion on large images. */ - if(version_compare(PHP_VERSION,'7.2.0') >= 0) { - $f = @fopen($filename,'rb'); + if(version_compare(PHP_VERSION, '7.2.0') >= 0) { + $f = @fopen($filename, 'rb'); } else { $f = $filename; @@ -281,21 +304,23 @@ abstract class photo_driver { } /** - * @brief orients current image based on exif orientation information + * @brief Orients current image based on exif orientation information. + * + * @param array $exif + * @return boolean true if oriented, otherwise false */ - public function orient($exif) { if(! ($this->is_valid() && $exif)) { return false; } - $ort = ((array_key_exists('IFD0',$exif)) ? $exif['IFD0']['Orientation'] : $exif['Orientation']); + $ort = ((array_key_exists('IFD0', $exif)) ? $exif['IFD0']['Orientation'] : $exif['Orientation']); if(! $ort) { return false; } - + switch($ort) { case 1: // nothing break; @@ -319,7 +344,7 @@ abstract class photo_driver { $this->flip(); $this->rotate(-90); break; - case 8: // 90 rotate left + case 8: // 90 rotate left $this->rotate(90); break; default: @@ -329,15 +354,21 @@ abstract class photo_driver { return true; } - + /** + * @brief Save photo to database. + * + * @param array $arr + * @param boolean $skipcheck (optional) default false + * @return boolean|array + */ public function save($arr, $skipcheck = false) { if(! ($skipcheck || $this->is_valid())) { - logger('attempt to store invalid photo.'); + logger('Attempt to store invalid photo.'); return false; } - $p = array(); + $p = []; $p['aid'] = ((intval($arr['aid'])) ? intval($arr['aid']) : 0); $p['uid'] = ((intval($arr['uid'])) ? intval($arr['uid']) : 0); @@ -351,11 +382,11 @@ abstract class photo_driver { $p['allow_gid'] = (($arr['allow_gid']) ? $arr['allow_gid'] : ''); $p['deny_cid'] = (($arr['deny_cid']) ? $arr['deny_cid'] : ''); $p['deny_gid'] = (($arr['deny_gid']) ? $arr['deny_gid'] : ''); - $p['edited'] = (($arr['edited']) ? $arr['edited'] : datetime_convert()); + $p['edited'] = (($arr['edited']) ? $arr['edited'] : datetime_convert()); $p['title'] = (($arr['title']) ? $arr['title'] : ''); $p['description'] = (($arr['description']) ? $arr['description'] : ''); $p['photo_usage'] = intval($arr['photo_usage']); - $p['os_storage'] = intval($arr['os_storage']); + $p['os_storage'] = intval($arr['os_storage']); $p['os_path'] = $arr['os_path']; $p['os_syspath'] = ((array_key_exists('os_syspath',$arr)) ? $arr['os_syspath'] : ''); $p['display_path'] = (($arr['display_path']) ? $arr['display_path'] : ''); @@ -364,7 +395,7 @@ abstract class photo_driver { $p['expires'] = (($arr['expires']) ? $arr['expires'] : gmdate('Y-m-d H:i:s', time() + get_config('system','photo_cache_time', 86400))); if(! intval($p['imgscale'])) - logger('save: ' . print_r($arr,true), LOGGER_DATA); + logger('save: ' . print_r($arr, true), LOGGER_DATA); $x = q("select id, created from photo where resource_id = '%s' and uid = %d and xchan = '%s' and imgscale = %d limit 1", dbesc($p['resource_id']), @@ -372,7 +403,7 @@ abstract class photo_driver { dbesc($p['xchan']), intval($p['imgscale']) ); - + if($x) { $p['created'] = (($x['created']) ? $x['created'] : $p['edited']); $r = q("UPDATE photo set @@ -388,7 +419,7 @@ abstract class photo_driver { height = %d, width = %d, content = '%s', - os_storage = %d, + os_storage = %d, filesize = %d, imgscale = %d, photo_usage = %d, @@ -463,33 +494,36 @@ abstract class photo_driver { dbescdate($p['expires']) ); } - logger('photo save ' . $p['imgscale'] . ' returned ' . intval($r)); + logger('Photo save ' . $p['imgscale'] . ' returned ' . intval($r)); + return $r; } } - - /** - * Guess image mimetype from filename or from Content-Type header + * @brief Guess image mimetype from filename or from Content-Type header. * - * @arg $filename string Image filename - * @arg $headers string Headers to check for Content-Type (from curl request) + * @param string $filename + * Image filename + * @param string $headers (optional) + * Headers to check for Content-Type (from curl request) + * @return null|string Guessed mimetype */ - function guess_image_type($filename, $headers = '') { // logger('Photo: guess_image_type: '.$filename . ($headers?' from curl headers':''), LOGGER_DEBUG); $type = null; - if ($headers) { - $hdrs=array(); - $h = explode("\n",$headers); + $m = null; + + if($headers) { + $hdrs = []; + $h = explode("\n", $headers); foreach ($h as $l) { - list($k,$v) = array_map("trim", explode(":", trim($l), 2)); + list($k,$v) = array_map('trim', explode(':', trim($l), 2)); $hdrs[strtolower($k)] = $v; } - logger('Curl headers: '.var_export($hdrs, true), LOGGER_DEBUG); - if (array_key_exists('content-type', $hdrs)) { + logger('Curl headers: ' .var_export($hdrs, true), LOGGER_DEBUG); + if(array_key_exists('content-type', $hdrs)) { $ph = photo_factory(''); $types = $ph->supportedTypes(); @@ -498,13 +532,13 @@ function guess_image_type($filename, $headers = '') { } } - if (is_null($type)){ + if(is_null($type)){ $ignore_imagick = get_config('system', 'ignore_imagick'); // Guessing from extension? Isn't that... dangerous? if(class_exists('Imagick') && file_exists($filename) && is_readable($filename) && !$ignore_imagick) { $v = Imagick::getVersion(); preg_match('/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $v['versionString'], $m); - if(version_compare($m[1],'6.6.7') >= 0) { + if(version_compare($m[1], '6.6.7') >= 0) { /** * Well, this not much better, * but at least it comes from the data inside the image, @@ -516,7 +550,7 @@ function guess_image_type($filename, $headers = '') { else { // earlier imagick versions have issues with scaling png's // don't log this because it will just fill the logfile. - // leave this note here so those who are looking for why + // leave this note here so those who are looking for why // we aren't using imagick can find it } } @@ -551,33 +585,36 @@ function guess_image_type($filename, $headers = '') { } logger('Photo: guess_image_type: filename = ' . $filename . ' type = ' . $type, LOGGER_DEBUG); - return $type; + return $type; } - -function delete_thing_photo($url,$ob_hash) { +/** + * @brief Delete thing photo from database. + * + * @param string $url + * @param string $ob_hash + * @return void + */ +function delete_thing_photo($url, $ob_hash) { $hash = basename($url); - $hash = substr($hash,0,strpos($hash,'-')); + $hash = substr($hash, 0, strpos($hash, '-')); - // hashes should be 32 bytes. + // hashes should be 32 bytes. if((! $ob_hash) || (strlen($hash) < 16)) - return; + return; - $r = q("delete from photo where xchan = '%s' and photo_usage = %d and resource_id = '%s'", + q("delete from photo where xchan = '%s' and photo_usage = %d and resource_id = '%s'", dbesc($ob_hash), intval(PHOTO_THING), dbesc($hash) ); - } - - /** - * @brief fetches an photo from external site and prepares its miniatures. + * @brief Fetches a photo from external site and prepares its miniatures. * * @param string $photo * external URL to fetch base image @@ -596,223 +633,215 @@ function delete_thing_photo($url,$ob_hash) { * * \e boolean \b 4 => TRUE if fetch failure * * \e string \b 5 => modification date */ +function import_xchan_photo($photo, $xchan, $thing = false, $force = false) { + $modified = ''; + $o = null; + + $flags = (($thing) ? PHOTO_THING : PHOTO_XCHAN); + $album = (($thing) ? 'Things' : 'Contact Photos'); + + logger('Updating channel photo from ' . $photo . ' for ' . $xchan, LOGGER_DEBUG); + + if($thing) { + $hash = photo_new_resource(); + } else { + $r = q("select resource_id, edited, mimetype from photo where xchan = '%s' and photo_usage = %d and imgscale = 4 limit 1", dbesc($xchan), intval(PHOTO_XCHAN)); + if($r) { + $hash = $r[0]['resource_id']; + $modified = $r[0]['edited']; + $type = $r[0]['mimetype']; + } else { + $hash = photo_new_resource(); + } + } -function import_xchan_photo($photo,$xchan,$thing = false,$force = false) { - - $modified = ''; - - $flags = (($thing) ? PHOTO_THING : PHOTO_XCHAN); - $album = (($thing) ? 'Things' : 'Contact Photos'); - - logger('import_xchan_photo: updating channel photo from ' . $photo . ' for ' . $xchan, LOGGER_DEBUG); - - if($thing) { - $hash = photo_new_resource(); - } - else { - $r = q("select resource_id, edited, mimetype from photo where xchan = '%s' and photo_usage = %d and imgscale = 4 limit 1", - dbesc($xchan), - intval(PHOTO_XCHAN) - ); - if($r) { - $hash = $r[0]['resource_id']; - $modified = $r[0]['edited']; - $type = $r[0]['mimetype']; - } - else { - $hash = photo_new_resource(); - } - } - - $photo_failure = false; - $img_str = ''; - - if($photo) { - $filename = basename($photo); - - if($force || $modified == '') { - $result = z_fetch_url($photo,true); - } - else { - $h = array('headers' => array("If-Modified-Since: " . gmdate("D, d M Y H:i:s", strtotime($modified . "Z")) . " GMT")); - $result = z_fetch_url($photo,true,0,$h); - } - - if($result['success']) { - $img_str = $result['body']; - $type = guess_image_type($photo, $result['header']); - $modified = gmdate('Y-m-d H:i:s', (preg_match('/last-modified: (.+) \S+/i', $result['header'], $o) ? strtotime($o[1] . 'Z') : time())); - if(is_null($type)) - $photo_failure = true; - } - elseif($result['return_code'] == 304) { - $photo = z_root() . '/photo/' . $hash . '-4'; - $thumb = z_root() . '/photo/' . $hash . '-5'; - $micro = z_root() . '/photo/' . $hash . '-6'; - } - else { - $photo_failure = true; - } - - } - else - $photo_failure = true; - - if(! $photo_failure && $result['return_code'] != 304) { - $img = photo_factory($img_str, $type); - if($img->is_valid()) { - $width = $img->getWidth(); - $height = $img->getHeight(); - - if($width && $height) { - if(($width / $height) > 1.2) { - // crop out the sides - $margin = $width - $height; - $img->cropImage(300,($margin / 2),0,$height,$height); - } - elseif(($height / $width) > 1.2) { - // crop out the bottom - $margin = $height - $width; - $img->cropImage(300,0,0,$width,$width); - - } - else { - $img->scaleImageSquare(300); - } - - } - else - $photo_failure = true; - - $p = array( - 'xchan' => $xchan, - 'resource_id' => $hash, - 'filename' => basename($photo), - 'album' => $album, - 'photo_usage' => $flags, - 'imgscale' => 4, - 'edited' => $modified - ); - - $r = $img->save($p); - - if($r === false) - $photo_failure = true; - - $img->scaleImage(80); - $p['imgscale'] = 5; - - $r = $img->save($p); - - if($r === false) - $photo_failure = true; - - $img->scaleImage(48); - $p['imgscale'] = 6; - - $r = $img->save($p); - - if($r === false) - $photo_failure = true; - - $photo = z_root() . '/photo/' . $hash . '-4'; - $thumb = z_root() . '/photo/' . $hash . '-5'; - $micro = z_root() . '/photo/' . $hash . '-6'; - } - else { - logger('import_xchan_photo: invalid image from ' . $photo); - $photo_failure = true; - } - } - if($photo_failure) { - $default = get_default_profile_photo(); - $photo = z_root() . '/' . $default; - $thumb = z_root() . '/' . get_default_profile_photo(80); - $micro = z_root() . '/' . get_default_profile_photo(48); - $type = 'image/png'; - $modified = gmdate('Y-m-d H:i:s', filemtime($default)); - } - - logger('HTTP code: ' . $result['return_code'] . '; modified: ' . $modified . '; failure: ' . ($photo_failure ? 'yes' : 'no') . '; URL: ' . $photo, LOGGER_DEBUG); - return(array($photo,$thumb,$micro,$type,$photo_failure,$modified)); - -} - -function import_channel_photo_from_url($photo,$aid,$uid) { + $photo_failure = false; + $img_str = ''; if($photo) { - $filename = basename($photo); - $result = z_fetch_url($photo,true); + if($force || $modified == '') { + $result = z_fetch_url($photo, true); + } else { + $h = [ + 'headers' => [ + 'If-Modified-Since: ' . gmdate('D, d M Y H:i:s', strtotime($modified . 'Z')) . ' GMT' + ] + ]; + $result = z_fetch_url($photo, true, 0, $h); + } if($result['success']) { $img_str = $result['body']; $type = guess_image_type($photo, $result['header']); + $modified = gmdate('Y-m-d H:i:s', (preg_match('/last-modified: (.+) \S+/i', $result['header'], $o) ? strtotime($o[1] . 'Z') : time())); + if(is_null($type)) + $photo_failure = true; + } elseif($result['return_code'] == 304) { + $photo = z_root() . '/photo/' . $hash . '-4'; + $thumb = z_root() . '/photo/' . $hash . '-5'; + $micro = z_root() . '/photo/' . $hash . '-6'; + } else { + $photo_failure = true; + } + } else { + $photo_failure = true; + } - if(is_null($type)) - $photo_failure = true; + if(!$photo_failure && $result['return_code'] != 304) { + $img = photo_factory($img_str, $type); + if($img->is_valid()) { + $width = $img->getWidth(); + $height = $img->getHeight(); + + if($width && $height) { + if(($width / $height) > 1.2) { + // crop out the sides + $margin = $width - $height; + $img->cropImage(300, ($margin / 2), 0, $height, $height); + } elseif(($height / $width) > 1.2) { + // crop out the bottom + $margin = $height - $width; + $img->cropImage(300, 0, 0, $width, $width); + } else { + $img->scaleImageSquare(300); + } + } else { + $photo_failure = true; + } + + $p = [ + 'xchan' => $xchan, + 'resource_id' => $hash, + 'filename' => basename($photo), + 'album' => $album, + 'photo_usage' => $flags, + 'imgscale' => 4, + 'edited' => $modified, + ]; + + $r = $img->save($p); + if($r === false) + $photo_failure = true; + + $img->scaleImage(80); + $p['imgscale'] = 5; + $r = $img->save($p); + if($r === false) + $photo_failure = true; + + $img->scaleImage(48); + $p['imgscale'] = 6; + $r = $img->save($p); + if($r === false) + $photo_failure = true; + + $photo = z_root() . '/photo/' . $hash . '-4'; + $thumb = z_root() . '/photo/' . $hash . '-5'; + $micro = z_root() . '/photo/' . $hash . '-6'; + } else { + logger('Invalid image from ' . $photo); + $photo_failure = true; } } - else { - $photo_failure = true; + if($photo_failure) { + $default = get_default_profile_photo(); + $photo = z_root() . '/' . $default; + $thumb = z_root() . '/' . get_default_profile_photo(80); + $micro = z_root() . '/' . get_default_profile_photo(48); + $type = 'image/png'; + $modified = gmdate('Y-m-d H:i:s', filemtime($default)); } - import_channel_photo($img_str,$type,$aid,$uid); + logger('HTTP code: ' . $result['return_code'] . '; modified: ' . $modified + . '; failure: ' . ($photo_failure ? 'yes' : 'no') . '; URL: ' . $photo, LOGGER_DEBUG); - return $type; + return(array($photo,$thumb,$micro,$type,$photo_failure,$modified)); } +/** + * @brief Import channel photo from a URL. + * + * @param string $photo URL to a photo + * @param int $aid + * @param int $uid channel_id + * @return null|string Guessed image mimetype or null. + */ +function import_channel_photo_from_url($photo, $aid, $uid) { + $type = null; -function import_channel_photo($photo,$type,$aid,$uid) { + if($photo) { + $result = z_fetch_url($photo, true); + + if($result['success']) { + $img_str = $result['body']; + $type = guess_image_type($photo, $result['header']); - logger('import_channel_photo: importing channel photo for ' . $uid, LOGGER_DEBUG); + import_channel_photo($img_str, $type, $aid, $uid); + } + } - $hash = photo_new_resource(); + return $type; +} - $photo_failure = false; +/** + * @brief Import a channel photo and prepare its miniatures. + * + * @param string $photo Image data + * @param string $type + * @param int $aid + * @param int $uid channel_id + * @return boolean|string false on failure, otherwise resource_id of photo + */ +function import_channel_photo($photo, $type, $aid, $uid) { + logger('Importing channel photo for ' . $uid, LOGGER_DEBUG); + $photo_failure = false; + $hash = photo_new_resource(); $filename = $hash; $img = photo_factory($photo, $type); if($img->is_valid()) { + // config array for image save method + $p = [ + 'aid' => $aid, + 'uid' => $uid, + 'resource_id' => $hash, + 'filename' => $filename, + 'album' => t('Profile Photos'), + 'photo_usage' => PHOTO_PROFILE, + 'imgscale' => 4, + ]; + + // photo size $img->scaleImageSquare(300); - - $p = array('aid' => $aid, 'uid' => $uid, 'resource_id' => $hash, 'filename' => $filename, 'album' => t('Profile Photos'), 'photo_usage' => PHOTO_PROFILE, 'imgscale' => 4); - $r = $img->save($p); - if($r === false) $photo_failure = true; + // thumb size $img->scaleImage(80); $p['imgscale'] = 5; - $r = $img->save($p); - if($r === false) $photo_failure = true; + // micro size $img->scaleImage(48); $p['imgscale'] = 6; - $r = $img->save($p); - if($r === false) $photo_failure = true; - } - else { - logger('import_channel_photo: invalid image.'); + } else { + logger('Invalid image.'); $photo_failure = true; } - //return(($photo_failure)? false : true); - if($photo_failure) return false; else return $hash; - } -- cgit v1.2.3 From e6dadb215e9e08491ae57ab851960a0973d3f704 Mon Sep 17 00:00:00 2001 From: Klaus Weidenbach Date: Tue, 5 Feb 2019 23:03:03 +0100 Subject: Refactor photo_driver to use namespaces. Add simple UnitTest, but it is not yet very meaningful. --- Zotlabs/Photo/PhotoDriver.php | 468 +++++++++++++++++++++++++++++++ Zotlabs/Photo/PhotoGd.php | 194 +++++++++++++ Zotlabs/Photo/PhotoImagick.php | 218 +++++++++++++++ include/photo/photo_driver.php | 470 +------------------------------- include/photo/photo_gd.php | 162 ----------- include/photo/photo_imagick.php | 213 --------------- tests/unit/Photo/PhotoGdTest.php | 147 ++++++++++ tests/unit/includes/PhotodriverTest.php | 39 +++ 8 files changed, 1078 insertions(+), 833 deletions(-) create mode 100644 Zotlabs/Photo/PhotoDriver.php create mode 100644 Zotlabs/Photo/PhotoGd.php create mode 100644 Zotlabs/Photo/PhotoImagick.php delete mode 100644 include/photo/photo_gd.php delete mode 100644 include/photo/photo_imagick.php create mode 100644 tests/unit/Photo/PhotoGdTest.php create mode 100644 tests/unit/includes/PhotodriverTest.php diff --git a/Zotlabs/Photo/PhotoDriver.php b/Zotlabs/Photo/PhotoDriver.php new file mode 100644 index 000000000..e2e143f8d --- /dev/null +++ b/Zotlabs/Photo/PhotoDriver.php @@ -0,0 +1,468 @@ +types = $this->supportedTypes(); + if(! array_key_exists($type, $this->types)) { + $type = 'image/jpeg'; + } + $this->type = $type; + $this->valid = false; + $this->load($data, $type); + } + + public function __destruct() { + if($this->is_valid()) + $this->destroy(); + } + + /** + * @brief Is it a valid image object. + * + * @return boolean + */ + public function is_valid() { + return $this->valid; + } + + /** + * @brief Get the width of the image. + * + * @return boolean|number Width of image in pixels, or false on failure + */ + public function getWidth() { + if(! $this->is_valid()) + return false; + + return $this->width; + } + + /** + * @brief Get the height of the image. + * + * @return boolean|number Height of image in pixels, or false on failure + */ + public function getHeight() { + if(! $this->is_valid()) + return false; + + return $this->height; + } + + /** + * @brief Saves the image resource to a file in filesystem. + * + * @param string $path Path and filename where to save the image + * @return boolean False on failure, otherwise true + */ + public function saveImage($path) { + if(! $this->is_valid()) + return false; + + return (file_put_contents($path, $this->imageString()) ? true : false); + } + + /** + * @brief Return mimetype of the image resource. + * + * @return boolean|string False on failure, otherwise mimetype. + */ + public function getType() { + if(! $this->is_valid()) + return false; + + return $this->type; + } + + /** + * @brief Return file extension of the image resource. + * + * @return boolean|string False on failure, otherwise file extension. + */ + public function getExt() { + if(! $this->is_valid()) + return false; + + return $this->types[$this->getType()]; + } + + /** + * @brief Scale image to max pixel size in either dimension. + * + * @param int $max maximum pixel size in either dimension + * @param boolean $float_height (optional) + * If true allow height to float to any length on tall images, constraining + * only the width + * @return boolean|void false on failure, otherwise void + */ + public function scaleImage($max, $float_height = true) { + if(! $this->is_valid()) + return false; + + $width = $this->width; + $height = $this->height; + + $dest_width = $dest_height = 0; + + if((! $width) || (! $height)) + return false; + + if($width > $max && $height > $max) { + + // very tall image (greater than 16:9) + // constrain the width - let the height float. + + if(((($height * 9) / 16) > $width) && ($float_height)) { + $dest_width = $max; + $dest_height = intval(($height * $max) / $width); + } // else constrain both dimensions + elseif($width > $height) { + $dest_width = $max; + $dest_height = intval(($height * $max) / $width); + } else { + $dest_width = intval(($width * $max) / $height); + $dest_height = $max; + } + } else { + if($width > $max) { + $dest_width = $max; + $dest_height = intval(($height * $max) / $width); + } else { + if($height > $max) { + + // very tall image (greater than 16:9) + // but width is OK - don't do anything + + if(((($height * 9) / 16) > $width) && ($float_height)) { + $dest_width = $width; + $dest_height = $height; + } else { + $dest_width = intval(($width * $max) / $height); + $dest_height = $max; + } + } else { + $dest_width = $width; + $dest_height = $height; + } + } + } + $this->doScaleImage($dest_width, $dest_height); + } + + public function scaleImageUp($min) { + if(! $this->is_valid()) { + return false; + } + + $width = $this->width; + $height = $this->height; + + $dest_width = $dest_height = 0; + + if((! $width) || (! $height)) + return false; + + if($width < $min && $height < $min) { + if($width > $height) { + $dest_width = $min; + $dest_height = intval(($height * $min) / $width); + } else { + $dest_width = intval(($width * $min) / $height); + $dest_height = $min; + } + } else { + if($width < $min) { + $dest_width = $min; + $dest_height = intval(($height * $min) / $width); + } else { + if($height < $min) { + $dest_width = intval(($width * $min) / $height); + $dest_height = $min; + } else { + $dest_width = $width; + $dest_height = $height; + } + } + } + $this->doScaleImage($dest_width, $dest_height); + } + + /** + * @brief Scales image to a square. + * + * @param int $dim Pixel of square image + * @return boolean|void false on failure, otherwise void + */ + public function scaleImageSquare($dim) { + if(! $this->is_valid()) + return false; + + $this->doScaleImage($dim, $dim); + } + + /** + * @brief Reads exif data from a given filename. + * + * @param string $filename + * @return boolean|array + */ + public function exif($filename) { + if((! function_exists('exif_read_data')) + || (! in_array($this->getType(), ['image/jpeg', 'image/tiff']))) + { + return false; + } + + /* + * PHP 7.2 allows you to use a stream resource, which should reduce/avoid + * memory exhaustion on large images. + */ + + if(version_compare(PHP_VERSION, '7.2.0') >= 0) { + $f = @fopen($filename, 'rb'); + } else { + $f = $filename; + } + + if($f) { + return @exif_read_data($f, null, true); + } + + return false; + } + + /** + * @brief Orients current image based on exif orientation information. + * + * @param array $exif + * @return boolean true if oriented, otherwise false + */ + public function orient($exif) { + if(! ($this->is_valid() && $exif)) { + return false; + } + + $ort = ((array_key_exists('IFD0', $exif)) ? $exif['IFD0']['Orientation'] : $exif['Orientation']); + + if(! $ort) { + return false; + } + + switch($ort) { + case 1 : // nothing + break; + case 2 : // horizontal flip + $this->flip(); + break; + case 3 : // 180 rotate left + $this->rotate(180); + break; + case 4 : // vertical flip + $this->flip(false, true); + break; + case 5 : // vertical flip + 90 rotate right + $this->flip(false, true); + $this->rotate(-90); + break; + case 6 : // 90 rotate right + $this->rotate(-90); + break; + case 7 : // horizontal flip + 90 rotate right + $this->flip(); + $this->rotate(-90); + break; + case 8 : // 90 rotate left + $this->rotate(90); + break; + default : + break; + } + + return true; + } + + /** + * @brief Save photo to database. + * + * @param array $arr + * @param boolean $skipcheck (optional) default false + * @return boolean|array + */ + public function save($arr, $skipcheck = false) { + if(! ($skipcheck || $this->is_valid())) { + logger('Attempt to store invalid photo.'); + return false; + } + + $p = []; + + $p['aid'] = ((intval($arr['aid'])) ? intval($arr['aid']) : 0); + $p['uid'] = ((intval($arr['uid'])) ? intval($arr['uid']) : 0); + $p['xchan'] = (($arr['xchan']) ? $arr['xchan'] : ''); + $p['resource_id'] = (($arr['resource_id']) ? $arr['resource_id'] : ''); + $p['filename'] = (($arr['filename']) ? $arr['filename'] : ''); + $p['mimetype'] = (($arr['mimetype']) ? $arr['mimetype'] : $this->getType()); + $p['album'] = (($arr['album']) ? $arr['album'] : ''); + $p['imgscale'] = ((intval($arr['imgscale'])) ? intval($arr['imgscale']) : 0); + $p['allow_cid'] = (($arr['allow_cid']) ? $arr['allow_cid'] : ''); + $p['allow_gid'] = (($arr['allow_gid']) ? $arr['allow_gid'] : ''); + $p['deny_cid'] = (($arr['deny_cid']) ? $arr['deny_cid'] : ''); + $p['deny_gid'] = (($arr['deny_gid']) ? $arr['deny_gid'] : ''); + $p['edited'] = (($arr['edited']) ? $arr['edited'] : datetime_convert()); + $p['title'] = (($arr['title']) ? $arr['title'] : ''); + $p['description'] = (($arr['description']) ? $arr['description'] : ''); + $p['photo_usage'] = intval($arr['photo_usage']); + $p['os_storage'] = intval($arr['os_storage']); + $p['os_path'] = $arr['os_path']; + $p['os_syspath'] = ((array_key_exists('os_syspath', $arr)) ? $arr['os_syspath'] : ''); + $p['display_path'] = (($arr['display_path']) ? $arr['display_path'] : ''); + $p['width'] = (($arr['width']) ? $arr['width'] : $this->getWidth()); + $p['height'] = (($arr['height']) ? $arr['height'] : $this->getHeight()); + $p['expires'] = (($arr['expires']) ? $arr['expires'] : gmdate('Y-m-d H:i:s', time() + get_config('system', 'photo_cache_time', 86400))); + + if(! intval($p['imgscale'])) + logger('save: ' . print_r($arr, true), LOGGER_DATA); + + $x = q("select id, created from photo where resource_id = '%s' and uid = %d and xchan = '%s' and imgscale = %d limit 1", dbesc($p['resource_id']), intval($p['uid']), dbesc($p['xchan']), intval($p['imgscale'])); + + if($x) { + $p['created'] = (($x['created']) ? $x['created'] : $p['edited']); + $r = q("UPDATE photo set + aid = %d, + uid = %d, + xchan = '%s', + resource_id = '%s', + created = '%s', + edited = '%s', + filename = '%s', + mimetype = '%s', + album = '%s', + height = %d, + width = %d, + content = '%s', + os_storage = %d, + filesize = %d, + imgscale = %d, + photo_usage = %d, + title = '%s', + description = '%s', + os_path = '%s', + display_path = '%s', + allow_cid = '%s', + allow_gid = '%s', + deny_cid = '%s', + deny_gid = '%s', + expires = '%s' + where id = %d", + intval($p['aid']), intval($p['uid']), dbesc($p['xchan']), dbesc($p['resource_id']), dbescdate($p['created']), dbescdate($p['edited']), dbesc(basename($p['filename'])), dbesc($p['mimetype']), dbesc($p['album']), intval($p['height']), intval($p['width']), (intval($p['os_storage']) ? dbescbin($p['os_syspath']) : dbescbin($this->imageString())), intval($p['os_storage']), (intval($p['os_storage']) ? @filesize($p['os_syspath']) : strlen($this->imageString())), intval($p['imgscale']), intval($p['photo_usage']), dbesc($p['title']), dbesc($p['description']), dbesc($p['os_path']), dbesc($p['display_path']), dbesc($p['allow_cid']), dbesc($p['allow_gid']), dbesc($p['deny_cid']), dbesc($p['deny_gid']), dbescdate($p['expires']), intval($x[0]['id'])); + } else { + $p['created'] = (($arr['created']) ? $arr['created'] : $p['edited']); + $r = q("INSERT INTO photo + ( aid, uid, xchan, resource_id, created, edited, filename, mimetype, album, height, width, content, os_storage, filesize, imgscale, photo_usage, title, description, os_path, display_path, allow_cid, allow_gid, deny_cid, deny_gid, expires ) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', %d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )", intval($p['aid']), intval($p['uid']), dbesc($p['xchan']), dbesc($p['resource_id']), dbescdate($p['created']), dbescdate($p['edited']), dbesc(basename($p['filename'])), dbesc($p['mimetype']), dbesc($p['album']), intval($p['height']), intval($p['width']), (intval($p['os_storage']) ? dbescbin($p['os_syspath']) : dbescbin($this->imageString())), intval($p['os_storage']), (intval($p['os_storage']) ? @filesize($p['os_syspath']) : strlen($this->imageString())), intval($p['imgscale']), intval($p['photo_usage']), dbesc($p['title']), dbesc($p['description']), dbesc($p['os_path']), dbesc($p['display_path']), dbesc($p['allow_cid']), dbesc($p['allow_gid']), dbesc($p['deny_cid']), dbesc($p['deny_gid']), dbescdate($p['expires'])); + } + logger('Photo save imgscale ' . $p['imgscale'] . ' returned ' . intval($r)); + + return $r; + } + +} diff --git a/Zotlabs/Photo/PhotoGd.php b/Zotlabs/Photo/PhotoGd.php new file mode 100644 index 000000000..1143c565c --- /dev/null +++ b/Zotlabs/Photo/PhotoGd.php @@ -0,0 +1,194 @@ +valid = false; + if(! $data) + return; + + $this->image = @imagecreatefromstring($data); + if($this->image !== false) { + $this->valid = true; + $this->setDimensions(); + imagealphablending($this->image, false); + imagesavealpha($this->image, true); + } + } + + protected function setDimensions() { + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + /** + * @brief GD driver does not preserve EXIF, so not need to clear it. + * + * @return void + */ + public function clearexif() { + return; + } + + protected function destroy() { + if($this->is_valid()) { + imagedestroy($this->image); + } + } + + /** + * @brief Return a PHP image resource of the current image. + * + * @see \Zotlabs\Photo\PhotoDriver::getImage() + * + * @return boolean|resource + */ + public function getImage() { + if(! $this->is_valid()) + return false; + + return $this->image; + } + + public function doScaleImage($dest_width, $dest_height) { + + $dest = imagecreatetruecolor($dest_width, $dest_height); + $width = imagesx($this->image); + $height = imagesy($this->image); + + imagealphablending($dest, false); + imagesavealpha($dest, true); + if($this->type == 'image/png') + imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + + imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); + if($this->image) + imagedestroy($this->image); + + $this->image = $dest; + $this->setDimensions(); + } + + public function rotate($degrees) { + if(! $this->is_valid()) + return false; + + $this->image = imagerotate($this->image, $degrees, 0); + $this->setDimensions(); + } + + public function flip($horiz = true, $vert = false) { + if(! $this->is_valid()) + return false; + + $w = imagesx($this->image); + $h = imagesy($this->image); + $flipped = imagecreate($w, $h); + if($horiz) { + for($x = 0; $x < $w; $x++) { + imagecopy($flipped, $this->image, $x, 0, $w - $x - 1, 0, 1, $h); + } + } + if($vert) { + for($y = 0; $y < $h; $y++) { + imagecopy($flipped, $this->image, 0, $y, 0, $h - $y - 1, $w, 1); + } + } + $this->image = $flipped; + $this->setDimensions(); // Shouldn't really be necessary + } + + public function cropImage($max, $x, $y, $w, $h) { + if(!$this->is_valid()) + return false; + + $dest = imagecreatetruecolor($max, $max); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if($this->type == 'image/png') + imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + + imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h); + if($this->image) + imagedestroy($this->image); + + $this->image = $dest; + $this->setDimensions(); + } + + public function cropImageRect($maxx, $maxy, $x, $y, $w, $h) { + if(! $this->is_valid()) + return false; + + $dest = imagecreatetruecolor($maxx, $maxy); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if($this->type == 'image/png') + imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + + imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $maxx, $maxy, $w, $h); + if($this->image) + imagedestroy($this->image); + + $this->image = $dest; + $this->setDimensions(); + } + + /** + * {@inheritDoc} + * @see \Zotlabs\Photo\PhotoDriver::imageString() + */ + public function imageString() { + if(! $this->is_valid()) + return false; + + $quality = false; + + ob_start(); + + switch($this->getType()){ + case 'image/png': + $quality = get_config('system', 'png_quality'); + if((! $quality) || ($quality > 9)) + $quality = PNG_QUALITY; + + \imagepng($this->image, NULL, $quality); + break; + case 'image/jpeg': + // gd can lack imagejpeg(), but we verify during installation it is available + default: + $quality = get_config('system', 'jpeg_quality'); + if((! $quality) || ($quality > 100)) + $quality = JPEG_QUALITY; + + \imagejpeg($this->image, NULL, $quality); + break; + } + $string = ob_get_contents(); + ob_end_clean(); + + return $string; + } + +} diff --git a/Zotlabs/Photo/PhotoImagick.php b/Zotlabs/Photo/PhotoImagick.php new file mode 100644 index 000000000..373bac40f --- /dev/null +++ b/Zotlabs/Photo/PhotoImagick.php @@ -0,0 +1,218 @@ + 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif', + ]; + } + + private function get_FormatsMap() { + return [ + 'image/jpeg' => 'JPG', + 'image/png' => 'PNG', + 'image/gif' => 'GIF', + ]; + } + + + protected function load($data, $type) { + $this->valid = false; + $this->image = new \Imagick(); + + if(! $data) + return; + + try { + $this->image->readImageBlob($data); + } catch(\Exception $e) { + logger('Imagick readImageBlob() exception:' . print_r($e, true)); + return; + } + + /* + * Setup the image to the format it will be saved to + */ + + $map = $this->get_FormatsMap(); + $format = $map[$type]; + + if($this->image) { + $this->image->setFormat($format); + + // Always coalesce, if it is not a multi-frame image it won't hurt anyway + $this->image = $this->image->coalesceImages(); + + $this->valid = true; + $this->setDimensions(); + + /* + * setup the compression here, so we'll do it only once + */ + switch($this->getType()) { + case 'image/png': + $quality = get_config('system', 'png_quality'); + if((! $quality) || ($quality > 9)) + $quality = PNG_QUALITY; + /* + * From http://www.imagemagick.org/script/command-line-options.php#quality: + * + * 'For the MNG and PNG image formats, the quality value sets + * the zlib compression level (quality / 10) and filter-type (quality % 10). + * The default PNG "quality" is 75, which means compression level 7 with adaptive PNG filtering, + * unless the image has a color map, in which case it means compression level 7 with no PNG filtering' + */ + $quality = $quality * 10; + $this->image->setCompressionQuality($quality); + break; + case 'image/jpeg': + $quality = get_config('system', 'jpeg_quality'); + if((! $quality) || ($quality > 100)) + $quality = JPEG_QUALITY; + $this->image->setCompressionQuality($quality); + default: + break; + } + } + } + + protected function destroy() { + if($this->is_valid()) { + $this->image->clear(); + $this->image->destroy(); + } + } + + protected function setDimensions() { + $this->width = $this->image->getImageWidth(); + $this->height = $this->image->getImageHeight(); + } + + /** + * @brief Strips the image of all profiles and comments. + * + * Keep ICC profile for better colors. + * + * @see \Zotlabs\Photo\PhotoDriver::clearexif() + */ + public function clearexif() { + $profiles = $this->image->getImageProfiles('icc', true); + + $this->image->stripImage(); + + if(! empty($profiles)) { + $this->image->profileImage('icc', $profiles['icc']); + } + } + + + /** + * @brief Return a \Imagick object of the current image. + * + * @see \Zotlabs\Photo\PhotoDriver::getImage() + * + * @return boolean|\Imagick + */ + public function getImage() { + if(! $this->is_valid()) + return false; + + $this->image = $this->image->deconstructImages(); + return $this->image; + } + + public function doScaleImage($dest_width, $dest_height) { + /* + * If it is not animated, there will be only one iteration here, + * so don't bother checking + */ + // Don't forget to go back to the first frame + $this->image->setFirstIterator(); + do { + $this->image->scaleImage($dest_width, $dest_height); + } while($this->image->nextImage()); + + $this->setDimensions(); + } + + public function rotate($degrees) { + if(! $this->is_valid()) + return false; + + $this->image->setFirstIterator(); + do { + // ImageMagick rotates in the opposite direction of imagerotate() + $this->image->rotateImage(new \ImagickPixel(), -$degrees); + } while($this->image->nextImage()); + + $this->setDimensions(); + } + + public function flip($horiz = true, $vert = false) { + if(! $this->is_valid()) + return false; + + $this->image->setFirstIterator(); + do { + if($horiz) $this->image->flipImage(); + if($vert) $this->image->flopImage(); + } while($this->image->nextImage()); + + $this->setDimensions(); // Shouldn't really be necessary + } + + public function cropImage($max,$x,$y,$w,$h) { + if(!$this->is_valid()) + return false; + + $this->image->setFirstIterator(); + do { + $this->image->cropImage($w, $h, $x, $y); + /* + * We need to remove the canvas, + * or the image is not resized to the crop: + * http://php.net/manual/en/imagick.cropimage.php#97232 + */ + $this->image->setImagePage(0, 0, 0, 0); + } while($this->image->nextImage()); + + $this->doScaleImage($max, $max); + } + + public function cropImageRect($maxx, $maxy, $x, $y, $w, $h) { + if(! $this->is_valid()) + return false; + + $this->image->setFirstIterator(); + do { + $this->image->cropImage($w, $h, $x, $y); + /* + * We need to remove the canvas, + * or the image is not resized to the crop: + * http://php.net/manual/en/imagick.cropimage.php#97232 + */ + $this->image->setImagePage(0, 0, 0, 0); + } while($this->image->nextImage()); + + $this->doScaleImage($maxx, $maxy); + } + + public function imageString() { + if(! $this->is_valid()) + return false; + + /* Clean it */ + $this->image = $this->image->deconstructImages(); + + return $this->image->getImagesBlob(); + } + +} diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php index 718bf875b..c11580bdc 100644 --- a/include/photo/photo_driver.php +++ b/include/photo/photo_driver.php @@ -1,15 +1,19 @@ = 0) { - require_once('include/photo/photo_imagick.php'); - $ph = new photo_imagick($data, $type); + $ph = new PhotoImagick($data, $type); } else { // earlier imagick versions have issues with scaling png's // don't log this because it will just fill the logfile. @@ -45,462 +48,13 @@ function photo_factory($data, $type = null) { } if(! $ph) { - require_once('include/photo/photo_gd.php'); - $ph = new photo_gd($data, $type); + $ph = new PhotoGd($data, $type); } return $ph; } -/** - * @brief Abstract photo driver class. - * - */ -abstract class photo_driver { - - protected $image; - protected $width; - protected $height; - protected $valid; - protected $type; - protected $types; - - abstract function supportedTypes(); - - abstract function load($data,$type); - - abstract function destroy(); - - abstract function setDimensions(); - - abstract function getImage(); - - abstract function doScaleImage($new_width,$new_height); - - abstract function rotate($degrees); - - abstract function flip($horiz = true, $vert = false); - - abstract function cropImage($max,$x,$y,$w,$h); - - abstract function cropImageRect($maxx,$maxy,$x,$y,$w,$h); - - abstract function imageString(); - - abstract function clearexif(); - - public function __construct($data, $type='') { - $this->types = $this->supportedTypes(); - if (! array_key_exists($type, $this->types)){ - $type = 'image/jpeg'; - } - $this->type = $type; - $this->valid = false; - $this->load($data, $type); - } - - public function __destruct() { - if($this->is_valid()) - $this->destroy(); - } - - public function is_valid() { - return $this->valid; - } - - public function getWidth() { - if(!$this->is_valid()) - return FALSE; - - return $this->width; - } - - public function getHeight() { - if(!$this->is_valid()) - return FALSE; - - return $this->height; - } - - - public function saveImage($path) { - if(!$this->is_valid()) - return FALSE; - - return (file_put_contents($path, $this->imageString()) ? true : false); - } - - - public function getType() { - if(!$this->is_valid()) - return FALSE; - - return $this->type; - } - - public function getExt() { - if(!$this->is_valid()) - return FALSE; - - return $this->types[$this->getType()]; - } - - /** - * @brief Scale image to max pixel size in either dimension. - * - * @param int $max maximum pixel size in either dimension - * @param boolean $float_height (optional) - * if true allow height to float to any length on tall images, constraining - * only the width - * @return boolean|void false on failure, otherwise void - */ - public function scaleImage($max, $float_height = true) { - if(!$this->is_valid()) - return FALSE; - - $width = $this->width; - $height = $this->height; - - $dest_width = $dest_height = 0; - - if((! $width)|| (! $height)) - return FALSE; - - if($width > $max && $height > $max) { - - // very tall image (greater than 16:9) - // constrain the width - let the height float. - - if(((($height * 9) / 16) > $width) && ($float_height)) { - $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); - } - - // else constrain both dimensions - - elseif($width > $height) { - $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); - } - else { - $dest_width = intval(( $width * $max ) / $height); - $dest_height = $max; - } - } - else { - if( $width > $max ) { - $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); - } - else { - if( $height > $max ) { - - // very tall image (greater than 16:9) - // but width is OK - don't do anything - - if(((($height * 9) / 16) > $width) && ($float_height)) { - $dest_width = $width; - $dest_height = $height; - } - else { - $dest_width = intval(( $width * $max ) / $height); - $dest_height = $max; - } - } - else { - $dest_width = $width; - $dest_height = $height; - } - } - } - $this->doScaleImage($dest_width,$dest_height); - } - - public function scaleImageUp($min) { - if(!$this->is_valid()) - return FALSE; - - $width = $this->width; - $height = $this->height; - - $dest_width = $dest_height = 0; - - if((! $width)|| (! $height)) - return FALSE; - - if($width < $min && $height < $min) { - if($width > $height) { - $dest_width = $min; - $dest_height = intval(( $height * $min ) / $width); - } - else { - $dest_width = intval(( $width * $min ) / $height); - $dest_height = $min; - } - } - else { - if( $width < $min ) { - $dest_width = $min; - $dest_height = intval(( $height * $min ) / $width); - } - else { - if( $height < $min ) { - $dest_width = intval(( $width * $min ) / $height); - $dest_height = $min; - } - else { - $dest_width = $width; - $dest_height = $height; - } - } - } - $this->doScaleImage($dest_width,$dest_height); - } - - /** - * @brief Scales image to a square. - * - * @param int $dim Pixel of squre image - * @return boolean|void false on failure, otherwise void - */ - public function scaleImageSquare($dim) { - if(!$this->is_valid()) - return FALSE; - - $this->doScaleImage($dim, $dim); - } - - /** - * @brief reads exif data from filename. - * - * @param string $filename - * @return boolean|array - */ - public function exif($filename) { - - if((! function_exists('exif_read_data')) - || (! in_array($this->getType(), [ 'image/jpeg' , 'image/tiff'] ))) { - return false; - } - - /* - * PHP 7.2 allows you to use a stream resource, which should reduce/avoid - * memory exhaustion on large images. - */ - - if(version_compare(PHP_VERSION, '7.2.0') >= 0) { - $f = @fopen($filename, 'rb'); - } - else { - $f = $filename; - } - - if($f) { - return @exif_read_data($f,null,true); - } - - return false; - } - - /** - * @brief Orients current image based on exif orientation information. - * - * @param array $exif - * @return boolean true if oriented, otherwise false - */ - public function orient($exif) { - - if(! ($this->is_valid() && $exif)) { - return false; - } - - $ort = ((array_key_exists('IFD0', $exif)) ? $exif['IFD0']['Orientation'] : $exif['Orientation']); - - if(! $ort) { - return false; - } - - switch($ort) { - case 1: // nothing - break; - case 2: // horizontal flip - $this->flip(); - break; - case 3: // 180 rotate left - $this->rotate(180); - break; - case 4: // vertical flip - $this->flip(false, true); - break; - case 5: // vertical flip + 90 rotate right - $this->flip(false, true); - $this->rotate(-90); - break; - case 6: // 90 rotate right - $this->rotate(-90); - break; - case 7: // horizontal flip + 90 rotate right - $this->flip(); - $this->rotate(-90); - break; - case 8: // 90 rotate left - $this->rotate(90); - break; - default: - break; - } - - return true; - } - - /** - * @brief Save photo to database. - * - * @param array $arr - * @param boolean $skipcheck (optional) default false - * @return boolean|array - */ - public function save($arr, $skipcheck = false) { - - if(! ($skipcheck || $this->is_valid())) { - logger('Attempt to store invalid photo.'); - return false; - } - - $p = []; - - $p['aid'] = ((intval($arr['aid'])) ? intval($arr['aid']) : 0); - $p['uid'] = ((intval($arr['uid'])) ? intval($arr['uid']) : 0); - $p['xchan'] = (($arr['xchan']) ? $arr['xchan'] : ''); - $p['resource_id'] = (($arr['resource_id']) ? $arr['resource_id'] : ''); - $p['filename'] = (($arr['filename']) ? $arr['filename'] : ''); - $p['mimetype'] = (($arr['mimetype']) ? $arr['mimetype'] : $this->getType()); - $p['album'] = (($arr['album']) ? $arr['album'] : ''); - $p['imgscale'] = ((intval($arr['imgscale'])) ? intval($arr['imgscale']) : 0); - $p['allow_cid'] = (($arr['allow_cid']) ? $arr['allow_cid'] : ''); - $p['allow_gid'] = (($arr['allow_gid']) ? $arr['allow_gid'] : ''); - $p['deny_cid'] = (($arr['deny_cid']) ? $arr['deny_cid'] : ''); - $p['deny_gid'] = (($arr['deny_gid']) ? $arr['deny_gid'] : ''); - $p['edited'] = (($arr['edited']) ? $arr['edited'] : datetime_convert()); - $p['title'] = (($arr['title']) ? $arr['title'] : ''); - $p['description'] = (($arr['description']) ? $arr['description'] : ''); - $p['photo_usage'] = intval($arr['photo_usage']); - $p['os_storage'] = intval($arr['os_storage']); - $p['os_path'] = $arr['os_path']; - $p['os_syspath'] = ((array_key_exists('os_syspath',$arr)) ? $arr['os_syspath'] : ''); - $p['display_path'] = (($arr['display_path']) ? $arr['display_path'] : ''); - $p['width'] = (($arr['width']) ? $arr['width'] : $this->getWidth()); - $p['height'] = (($arr['height']) ? $arr['height'] : $this->getHeight()); - $p['expires'] = (($arr['expires']) ? $arr['expires'] : gmdate('Y-m-d H:i:s', time() + get_config('system','photo_cache_time', 86400))); - - if(! intval($p['imgscale'])) - logger('save: ' . print_r($arr, true), LOGGER_DATA); - - $x = q("select id, created from photo where resource_id = '%s' and uid = %d and xchan = '%s' and imgscale = %d limit 1", - dbesc($p['resource_id']), - intval($p['uid']), - dbesc($p['xchan']), - intval($p['imgscale']) - ); - - if($x) { - $p['created'] = (($x['created']) ? $x['created'] : $p['edited']); - $r = q("UPDATE photo set - aid = %d, - uid = %d, - xchan = '%s', - resource_id = '%s', - created = '%s', - edited = '%s', - filename = '%s', - mimetype = '%s', - album = '%s', - height = %d, - width = %d, - content = '%s', - os_storage = %d, - filesize = %d, - imgscale = %d, - photo_usage = %d, - title = '%s', - description = '%s', - os_path = '%s', - display_path = '%s', - allow_cid = '%s', - allow_gid = '%s', - deny_cid = '%s', - deny_gid = '%s', - expires = '%s' - where id = %d", - - intval($p['aid']), - intval($p['uid']), - dbesc($p['xchan']), - dbesc($p['resource_id']), - dbescdate($p['created']), - dbescdate($p['edited']), - dbesc(basename($p['filename'])), - dbesc($p['mimetype']), - dbesc($p['album']), - intval($p['height']), - intval($p['width']), - (intval($p['os_storage']) ? dbescbin($p['os_syspath']) : dbescbin($this->imageString())), - intval($p['os_storage']), - (intval($p['os_storage']) ? @filesize($p['os_syspath']) : strlen($this->imageString())), - intval($p['imgscale']), - intval($p['photo_usage']), - dbesc($p['title']), - dbesc($p['description']), - dbesc($p['os_path']), - dbesc($p['display_path']), - dbesc($p['allow_cid']), - dbesc($p['allow_gid']), - dbesc($p['deny_cid']), - dbesc($p['deny_gid']), - dbescdate($p['expires']), - intval($x[0]['id']) - ); - } - else { - $p['created'] = (($arr['created']) ? $arr['created'] : $p['edited']); - $r = q("INSERT INTO photo - ( aid, uid, xchan, resource_id, created, edited, filename, mimetype, album, height, width, content, os_storage, filesize, imgscale, photo_usage, title, description, os_path, display_path, allow_cid, allow_gid, deny_cid, deny_gid, expires ) - VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', %d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )", - intval($p['aid']), - intval($p['uid']), - dbesc($p['xchan']), - dbesc($p['resource_id']), - dbescdate($p['created']), - dbescdate($p['edited']), - dbesc(basename($p['filename'])), - dbesc($p['mimetype']), - dbesc($p['album']), - intval($p['height']), - intval($p['width']), - (intval($p['os_storage']) ? dbescbin($p['os_syspath']) : dbescbin($this->imageString())), - intval($p['os_storage']), - (intval($p['os_storage']) ? @filesize($p['os_syspath']) : strlen($this->imageString())), - intval($p['imgscale']), - intval($p['photo_usage']), - dbesc($p['title']), - dbesc($p['description']), - dbesc($p['os_path']), - dbesc($p['display_path']), - dbesc($p['allow_cid']), - dbesc($p['allow_gid']), - dbesc($p['deny_cid']), - dbesc($p['deny_gid']), - dbescdate($p['expires']) - ); - } - logger('Photo save ' . $p['imgscale'] . ' returned ' . intval($r)); - - return $r; - } - -} - /** * @brief Guess image mimetype from filename or from Content-Type header. * @@ -519,7 +73,7 @@ function guess_image_type($filename, $headers = '') { $hdrs = []; $h = explode("\n", $headers); foreach ($h as $l) { - list($k,$v) = array_map('trim', explode(':', trim($l), 2)); + list($k, $v) = array_map('trim', explode(':', trim($l), 2)); $hdrs[strtolower($k)] = $v; } logger('Curl headers: ' .var_export($hdrs, true), LOGGER_DEBUG); @@ -566,7 +120,7 @@ function guess_image_type($filename, $headers = '') { } } - if(is_null($type) && (strpos($filename,'http') === false)) { + if(is_null($type) && (strpos($filename, 'http') === false)) { $size = getimagesize($filename); $ph = photo_factory(''); $types = $ph->supportedTypes(); @@ -756,7 +310,7 @@ function import_xchan_photo($photo, $xchan, $thing = false, $force = false) { logger('HTTP code: ' . $result['return_code'] . '; modified: ' . $modified . '; failure: ' . ($photo_failure ? 'yes' : 'no') . '; URL: ' . $photo, LOGGER_DEBUG); - return(array($photo,$thumb,$micro,$type,$photo_failure,$modified)); + return([$photo, $thumb, $micro, $type, $photo_failure, $modified]); } /** diff --git a/include/photo/photo_gd.php b/include/photo/photo_gd.php deleted file mode 100644 index e98ac2827..000000000 --- a/include/photo/photo_gd.php +++ /dev/null @@ -1,162 +0,0 @@ -valid = false; - if(! $data) - return; - - $this->image = @imagecreatefromstring($data); - if($this->image !== FALSE) { - $this->valid = true; - $this->setDimensions(); - imagealphablending($this->image, false); - imagesavealpha($this->image, true); - } - } - - function setDimensions() { - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - - public function clearexif() { - return; - } - - - public function destroy() { - if($this->is_valid()) { - imagedestroy($this->image); - } - } - - public function getImage() { - if(!$this->is_valid()) - return FALSE; - - return $this->image; - } - - public function doScaleImage($dest_width,$dest_height) { - - $dest = imagecreatetruecolor( $dest_width, $dest_height ); - $width = imagesx($this->image); - $height = imagesy($this->image); - - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->setDimensions(); - } - - public function rotate($degrees) { - if(!$this->is_valid()) - return FALSE; - - $this->image = imagerotate($this->image,$degrees,0); - $this->setDimensions(); - } - - public function flip($horiz = true, $vert = false) { - if(!$this->is_valid()) - return FALSE; - - $w = imagesx($this->image); - $h = imagesy($this->image); - $flipped = imagecreate($w, $h); - if($horiz) { - for ($x = 0; $x < $w; $x++) { - imagecopy($flipped, $this->image, $x, 0, $w - $x - 1, 0, 1, $h); - } - } - if($vert) { - for ($y = 0; $y < $h; $y++) { - imagecopy($flipped, $this->image, 0, $y, 0, $h - $y - 1, $w, 1); - } - } - $this->image = $flipped; - $this->setDimensions(); // Shouldn't really be necessary - } - - public function cropImage($max,$x,$y,$w,$h) { - if(!$this->is_valid()) - return FALSE; - - $dest = imagecreatetruecolor( $max, $max ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->setDimensions(); - } - - public function cropImageRect($maxx,$maxy,$x,$y,$w,$h) { - if(!$this->is_valid()) - return FALSE; - - $dest = imagecreatetruecolor( $maxx, $maxy ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $maxx, $maxy, $w, $h); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->setDimensions(); - } - - - - public function imageString() { - if(!$this->is_valid()) - return FALSE; - - $quality = FALSE; - - ob_start(); - - switch($this->getType()){ - case "image/png": - $quality = get_config('system','png_quality'); - if((! $quality) || ($quality > 9)) - $quality = PNG_QUALITY; - imagepng($this->image,NULL, $quality); - break; - case "image/jpeg": - default: - $quality = get_config('system','jpeg_quality'); - if((! $quality) || ($quality > 100)) - $quality = JPEG_QUALITY; - imagejpeg($this->image,NULL,$quality); - break; - } - $string = ob_get_contents(); - ob_end_clean(); - - return $string; - } - -} diff --git a/include/photo/photo_imagick.php b/include/photo/photo_imagick.php deleted file mode 100644 index cb3ad27fb..000000000 --- a/include/photo/photo_imagick.php +++ /dev/null @@ -1,213 +0,0 @@ - 'jpg', - 'image/png' => 'png', - 'image/gif' => 'gif' - ); - } - - public function get_FormatsMap() { - return array( - 'image/jpeg' => 'JPG', - 'image/png' => 'PNG', - 'image/gif' => 'GIF' - ); - } - - - function load($data, $type) { - $this->valid = false; - $this->image = new Imagick(); - - if(! $data) - return; - - try { - $this->image->readImageBlob($data); - } - catch (Exception $e) { - logger('imagick readImageBlob() exception:' . print_r($e,true)); - return; - } - - /** - * Setup the image to the format it will be saved to - */ - - $map = $this->get_FormatsMap(); - $format = $map[$type]; - - if($this->image) { - $this->image->setFormat($format); - - // Always coalesce, if it is not a multi-frame image it won't hurt anyway - $this->image = $this->image->coalesceImages(); - - - $this->valid = true; - $this->setDimensions(); - - /** - * setup the compression here, so we'll do it only once - */ - switch($this->getType()) { - case "image/png": - $quality = get_config('system','png_quality'); - if((! $quality) || ($quality > 9)) - $quality = PNG_QUALITY; - /** - * From http://www.imagemagick.org/script/command-line-options.php#quality: - * - * 'For the MNG and PNG image formats, the quality value sets - * the zlib compression level (quality / 10) and filter-type (quality % 10). - * The default PNG "quality" is 75, which means compression level 7 with adaptive PNG filtering, - * unless the image has a color map, in which case it means compression level 7 with no PNG filtering' - */ - $quality = $quality * 10; - $this->image->setCompressionQuality($quality); - break; - case "image/jpeg": - $quality = get_config('system','jpeg_quality'); - if((! $quality) || ($quality > 100)) - $quality = JPEG_QUALITY; - $this->image->setCompressionQuality($quality); - default: - break; - - } - } - } - - public function destroy() { - if($this->is_valid()) { - $this->image->clear(); - $this->image->destroy(); - } - } - - - public function setDimensions() { - $this->width = $this->image->getImageWidth(); - $this->height = $this->image->getImageHeight(); - } - - - public function clearexif() { - - $profiles = $this->image->getImageProfiles("icc", true); - - $this->image->stripImage(); - - if(!empty($profiles)) { - $this->image->profileImage("icc", $profiles['icc']); - } - } - - - - public function getImage() { - if(!$this->is_valid()) - return FALSE; - - $this->image = $this->image->deconstructImages(); - return $this->image; - } - - public function doScaleImage($dest_width,$dest_height) { - - /** - * If it is not animated, there will be only one iteration here, - * so don't bother checking - */ - // Don't forget to go back to the first frame - $this->image->setFirstIterator(); - do { - $this->image->scaleImage($dest_width, $dest_height); - } while ($this->image->nextImage()); - - $this->setDimensions(); - } - - public function rotate($degrees) { - if(!$this->is_valid()) - return FALSE; - - $this->image->setFirstIterator(); - do { - // ImageMagick rotates in the opposite direction of imagerotate() - $this->image->rotateImage(new ImagickPixel(), -$degrees); - } while ($this->image->nextImage()); - - $this->setDimensions(); - } - - public function flip($horiz = true, $vert = false) { - if(!$this->is_valid()) - return FALSE; - - $this->image->setFirstIterator(); - do { - if($horiz) $this->image->flipImage(); - if($vert) $this->image->flopImage(); - } while ($this->image->nextImage()); - - $this->setDimensions(); // Shouldn't really be necessary - } - - public function cropImage($max,$x,$y,$w,$h) { - if(!$this->is_valid()) - return FALSE; - - $this->image->setFirstIterator(); - do { - $this->image->cropImage($w, $h, $x, $y); - /** - * We need to remove the canvas, - * or the image is not resized to the crop: - * http://php.net/manual/en/imagick.cropimage.php#97232 - */ - $this->image->setImagePage(0, 0, 0, 0); - } while ($this->image->nextImage()); - - $this->doScaleImage($max,$max); - } - - public function cropImageRect($maxx,$maxy,$x,$y,$w,$h) { - if(!$this->is_valid()) - return FALSE; - - $this->image->setFirstIterator(); - do { - $this->image->cropImage($w, $h, $x, $y); - /** - * We need to remove the canvas, - * or the image is not resized to the crop: - * http://php.net/manual/en/imagick.cropimage.php#97232 - */ - $this->image->setImagePage(0, 0, 0, 0); - } while ($this->image->nextImage()); - - $this->doScaleImage($maxx,$maxy); - } - - public function imageString() { - if(!$this->is_valid()) - return FALSE; - - /* Clean it */ - $this->image = $this->image->deconstructImages(); - return $this->image->getImagesBlob(); - } - - - -} diff --git a/tests/unit/Photo/PhotoGdTest.php b/tests/unit/Photo/PhotoGdTest.php new file mode 100644 index 000000000..1d4f9467f --- /dev/null +++ b/tests/unit/Photo/PhotoGdTest.php @@ -0,0 +1,147 @@ +photoGd = new PhotoGd($data, 'image/png'); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() { + $this->photoGd = null; + + parent::tearDown(); + } + + /** + * Tests PhotoGd->supportedTypes() + * + * Without mocking gd this check is environment dependent. + * + public function testSupportedTypes() { + $sft = $this->photoGd->supportedTypes(); + + $this->assertArrayHasKey('image/jpeg', $sft); + $this->assertArrayHasKey('image/gif', $sft); + $this->assertArrayHasKey('image/png', $sft); + + $this->assertArrayNotHasKey('image/foo', $sft); + } + */ + + /** + * Tests PhotoGd->clearexif() + */ + public function testClearexifIsNotImplementedInGdAndDoesNotAlterImageOrReturnSomething() { + $data_before = $this->photoGd->getImage(); + $this->assertNull($this->photoGd->clearexif()); + $this->assertSame($data_before, $this->photoGd->getImage()); + } + + /** + * Tests PhotoGd->getImage() + */ + public function testGetimageReturnsAResource() { + $res = $this->photoGd->getImage(); + $this->assertIsResource($res); + $this->assertEquals('gd', get_resource_type($res)); + } + public function testGetimageReturnsFalseOnFailure() { + $this->photoGd = new PhotoGd(''); + $this->assertFalse($this->photoGd->getImage()); + } + + /** + * Tests PhotoGd->doScaleImage() + */ + public function testDoscaleImageSetsCorrectDimensions() { + $this->photoGd->doScaleImage(5, 8); + + $this->assertSame(5, $this->photoGd->getWidth()); + $this->assertSame(8, $this->photoGd->getHeight()); + } + + /** + * Tests PhotoGd->rotate() + */ + public function testRotate360DegreesCreatesANewImage() { + $data = $this->photoGd->getImage(); + $this->photoGd->rotate(360); + $this->assertNotEquals($data, $this->photoGd->getImage()); + } + + /** + * Tests PhotoGd->flip() + * + public function testFlip() { + // TODO Auto-generated PhotoGdTest->testFlip() + $this->markTestIncomplete("flip test not implemented"); + + $this->photoGd->flip(); + } + */ + + /** + * Tests PhotoGd->cropImageRect() + */ + public function testCropimagerectSetsCorrectDimensions() { + $this->photoGd->cropImageRect(10, 12, 1, 2, 11, 11); + + $this->assertSame(10, $this->photoGd->getWidth()); + $this->assertSame(12, $this->photoGd->getHeight()); + } + + /** + * Tests PhotoGd->imageString() + */ + public function testImagestringReturnsABinaryString() { + // Create a stub for global function get_config() + // get_config('system', 'png_quality') + // get_config('system', 'jpeg_quality'); + $gc = $this->getFunctionMock('Zotlabs\Photo', 'get_config'); + $gc->expects($this->once())->willReturnCallback( + function() { + switch($this->photoGd->getType()){ + case 'image/png': + return 7; + case 'image/jpeg': + default: + return 70; + } + } + ); + + $this->assertIsString($this->photoGd->imageString()); + } + +} diff --git a/tests/unit/includes/PhotodriverTest.php b/tests/unit/includes/PhotodriverTest.php new file mode 100644 index 000000000..6f6ad0ffe --- /dev/null +++ b/tests/unit/includes/PhotodriverTest.php @@ -0,0 +1,39 @@ +getFunctionMock(__NAMESPACE__, 'logger'); + //$logger->expects($this->once()); + + //$ph = \photo_factory('', 'image/bmp'); + //$this->assertNull($ph); + + $this->markTestIncomplete('Need to mock logger(), otherwise not unit testable.'); + } + + public function testPhotofactoryReturnsPhotogdIfConfigIgnore_imagickIsSet() { + // php-mock can not mock global functions which is called by a global function. + // If the calling function is in a namespace it would work. + //$gc = $this->getFunctionMock(__NAMESPACE__, 'get_config'); + // simulate get_config('system', 'ignore_imagick') configured + //$gc->expects($this->once())->willReturn(1) + + //$ph = \photo_factory(file_get_contents('images/hz-16.png'), 'image/png'); + //$this->assertInstanceOf(PhotoGd::class, $ph); + + $this->markTestIncomplete('Need to mock get_config(), otherwise not unit testable.'); + } +} \ No newline at end of file -- cgit v1.2.3 From b9df4c99cf9fcd1b16757309b4206ad084376aa2 Mon Sep 17 00:00:00 2001 From: Klaus Weidenbach Date: Mon, 11 Feb 2019 23:59:43 +0100 Subject: Remove duplicate code in PhotoDriver classes. cropImage is just a special case of cropImageRect, no need to duplicate all the code. --- Zotlabs/Photo/PhotoDriver.php | 34 ++++++++++++++++++++++++++++++++-- Zotlabs/Photo/PhotoGd.php | 18 ------------------ Zotlabs/Photo/PhotoImagick.php | 18 ------------------ 3 files changed, 32 insertions(+), 38 deletions(-) diff --git a/Zotlabs/Photo/PhotoDriver.php b/Zotlabs/Photo/PhotoDriver.php index e2e143f8d..c47a7c3b2 100644 --- a/Zotlabs/Photo/PhotoDriver.php +++ b/Zotlabs/Photo/PhotoDriver.php @@ -80,8 +80,18 @@ abstract class PhotoDriver { abstract public function flip($horiz = true, $vert = false); - abstract public function cropImage($max, $x, $y, $w, $h); - + /** + * @brief Crops the image. + * + * @param int $maxx width of the new image + * @param int $maxy height of the new image + * @param int $x x-offset for region + * @param int $y y-offset for region + * @param int $w width of region + * @param int $h height of region + * + * @return boolean|void false on failure + */ abstract public function cropImageRect($maxx, $maxy, $x, $y, $w, $h); /** @@ -299,6 +309,26 @@ abstract class PhotoDriver { $this->doScaleImage($dim, $dim); } + /** + * @brief Crops a square image. + * + * @see cropImageRect() + * + * @param int $max size of the new image + * @param int $x x-offset for region + * @param int $y y-offset for region + * @param int $w width of region + * @param int $h height of region + * + * @return boolean|void false on failure + */ + public function cropImage($max, $x, $y, $w, $h) { + if(! $this->is_valid()) + return false; + + $this->cropImageRect($max, $max, $x, $y, $w, $h); + } + /** * @brief Reads exif data from a given filename. * diff --git a/Zotlabs/Photo/PhotoGd.php b/Zotlabs/Photo/PhotoGd.php index 1143c565c..4054e1866 100644 --- a/Zotlabs/Photo/PhotoGd.php +++ b/Zotlabs/Photo/PhotoGd.php @@ -119,24 +119,6 @@ class PhotoGd extends PhotoDriver { $this->setDimensions(); // Shouldn't really be necessary } - public function cropImage($max, $x, $y, $w, $h) { - if(!$this->is_valid()) - return false; - - $dest = imagecreatetruecolor($max, $max); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if($this->type == 'image/png') - imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - - imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h); - if($this->image) - imagedestroy($this->image); - - $this->image = $dest; - $this->setDimensions(); - } - public function cropImageRect($maxx, $maxy, $x, $y, $w, $h) { if(! $this->is_valid()) return false; diff --git a/Zotlabs/Photo/PhotoImagick.php b/Zotlabs/Photo/PhotoImagick.php index 373bac40f..a7026e8ca 100644 --- a/Zotlabs/Photo/PhotoImagick.php +++ b/Zotlabs/Photo/PhotoImagick.php @@ -169,24 +169,6 @@ class PhotoImagick extends PhotoDriver { $this->setDimensions(); // Shouldn't really be necessary } - public function cropImage($max,$x,$y,$w,$h) { - if(!$this->is_valid()) - return false; - - $this->image->setFirstIterator(); - do { - $this->image->cropImage($w, $h, $x, $y); - /* - * We need to remove the canvas, - * or the image is not resized to the crop: - * http://php.net/manual/en/imagick.cropimage.php#97232 - */ - $this->image->setImagePage(0, 0, 0, 0); - } while($this->image->nextImage()); - - $this->doScaleImage($max, $max); - } - public function cropImageRect($maxx, $maxy, $x, $y, $w, $h) { if(! $this->is_valid()) return false; -- cgit v1.2.3