aboutsummaryrefslogtreecommitdiffstats
path: root/Zotlabs/Photo
diff options
context:
space:
mode:
authorKlaus Weidenbach <Klaus.Weidenbach@gmx.net>2019-02-05 23:03:03 +0100
committerKlaus Weidenbach <Klaus.Weidenbach@gmx.net>2019-02-14 22:06:42 +0100
commite6dadb215e9e08491ae57ab851960a0973d3f704 (patch)
treee24ac9e2a5037e23bbd5e97672ed55db21bbf399 /Zotlabs/Photo
parentd70bba28065fb3105543ef40d91f6db839d35e0f (diff)
downloadvolse-hubzilla-e6dadb215e9e08491ae57ab851960a0973d3f704.tar.gz
volse-hubzilla-e6dadb215e9e08491ae57ab851960a0973d3f704.tar.bz2
volse-hubzilla-e6dadb215e9e08491ae57ab851960a0973d3f704.zip
Refactor photo_driver to use namespaces.
Add simple UnitTest, but it is not yet very meaningful.
Diffstat (limited to 'Zotlabs/Photo')
-rw-r--r--Zotlabs/Photo/PhotoDriver.php468
-rw-r--r--Zotlabs/Photo/PhotoGd.php194
-rw-r--r--Zotlabs/Photo/PhotoImagick.php218
3 files changed, 880 insertions, 0 deletions
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 @@
+<?php
+
+namespace Zotlabs\Photo;
+
+/**
+ * @brief Abstract photo driver class.
+ *
+ * Inheritance seems not to be the best design pattern for such photo drivers.
+ */
+abstract class PhotoDriver {
+
+ /**
+ * @brief This variable keeps the image.
+ *
+ * For GD it is a PHP image resource.
+ * For ImageMagick it is an \Imagick object.
+ *
+ * @var resource|\Imagick
+ */
+ protected $image;
+
+ /**
+ * @var integer
+ */
+ protected $width;
+
+ /**
+ * @var integer
+ */
+ protected $height;
+
+ /**
+ * @var boolean
+ */
+ protected $valid;
+
+ /**
+ * @brief The mimetype of the image.
+ *
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * @brief Supported mimetypes by the used photo driver.
+ *
+ * @var array
+ */
+ protected $types;
+
+ /**
+ * @brief Return an array with supported mimetypes.
+ *
+ * @return array
+ * Associative array with mimetype as key and file extension as value.
+ */
+ abstract public function supportedTypes();
+
+ abstract protected function load($data, $type);
+
+ abstract protected function destroy();
+
+ abstract protected function setDimensions();
+
+ /**
+ * @brief Return the current image.
+ *
+ * @fixme Shouldn't his method be protected, because outside of the current
+ * driver it makes no sense at all because of the different return values.
+ *
+ * @return boolean|resource|\Imagick
+ * false on failure, a PHP image resource for GD driver, an \Imagick object
+ * for ImageMagick driver.
+ */
+ abstract public function getImage();
+
+ abstract public function doScaleImage($new_width, $new_height);
+
+ abstract public function rotate($degrees);
+
+ abstract public function flip($horiz = true, $vert = false);
+
+ abstract public function cropImage($max, $x, $y, $w, $h);
+
+ abstract public function cropImageRect($maxx, $maxy, $x, $y, $w, $h);
+
+ /**
+ * @brief Return a binary string from the image resource.
+ *
+ * @return string A Binary String.
+ */
+ abstract public function imageString();
+
+ abstract public function clearexif();
+
+
+ /**
+ * @brief PhotoDriver constructor.
+ *
+ * @param string $data Image
+ * @param string $type mimetype
+ */
+ 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();
+ }
+
+ /**
+ * @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 @@
+<?php
+
+namespace Zotlabs\Photo;
+
+/**
+ * @brief GD photo driver.
+ *
+ */
+class PhotoGd extends PhotoDriver {
+
+ /**
+ * {@inheritDoc}
+ * @see \Zotlabs\Photo\PhotoDriver::supportedTypes()
+ */
+ public function supportedTypes() {
+ $t = [];
+ $t['image/jpeg'] = 'jpg';
+ if(imagetypes() & IMG_PNG)
+ $t['image/png'] = 'png';
+ if(imagetypes() & IMG_GIF)
+ $t['image/gif'] = 'gif';
+
+ return $t;
+ }
+
+ protected function load($data, $type) {
+ $this->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 @@
+<?php
+
+namespace Zotlabs\Photo;
+
+/**
+ * @brief ImageMagick photo driver.
+ */
+class PhotoImagick extends PhotoDriver {
+
+ public function supportedTypes() {
+ return [
+ 'image/jpeg' => '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();
+ }
+
+}