Passed
Push — master ( 8e0f36...cdb6de )
by Aimeos
06:20
created

Imagick::__construct()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 32
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 16
nc 16
nop 3
dl 0
loc 32
rs 9.4222
c 1
b 0
f 0
1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2017-2022
6
 * @package MW
7
 * @subpackage Media
8
 */
9
10
11
namespace Aimeos\MW\Media\Image;
12
13
14
/**
15
 * Default image class using ImageMagick.
16
 *
17
 * @package MW
18
 * @subpackage Media
19
 */
20
class Imagick
21
	extends \Aimeos\MW\Media\Image\Base
22
	implements \Aimeos\MW\Media\Image\Iface
23
{
24
	private $image;
25
	private $options;
26
27
	private static $wmimg;
28
	private static $wmpath;
29
	private static $wmimages;
30
31
32
	/**
33
	 * Initializes the new image object.
34
	 *
35
	 * @param string $content File content
36
	 * @param string $mimetype Mime type of the media data
37
	 * @param array $options Associative list of configuration options
38
	 * @throws \Aimeos\MW\Media\Exception If image couldn't be retrieved from the given file name
39
	 */
40
	public function __construct( string $content, string $mimetype, array $options )
41
	{
42
		parent::__construct( $mimetype );
43
44
		try
45
		{
46
			$this->image = new \Imagick();
47
			$this->image->readImageBlob( $content );
48
49
			if( in_array( $mimetype, ['image/gif', 'image/png', 'image/webp'] ) )
50
			{
51
				$this->image->setImageAlphaChannel( \Imagick::ALPHACHANNEL_ACTIVATE );
52
				$this->image->setImageBackgroundColor( $options['image']['background'] ?? 'transparent' );
53
			}
54
			else
55
			{
56
				$this->image->setImageBackgroundColor( $options['image']['background'] ?? '#ffffff' );
57
			}
58
59
			if( isset( $options['image']['watermark'] ) && self::$wmpath !== $options['image']['watermark'] )
60
			{
61
					self::$wmimg = new \Imagick( realpath( $options['image']['watermark'] ) );
62
					self::$wmpath = $options['image']['watermark'];
63
					self::$wmimages = [];
64
			}
65
		}
66
		catch( \Exception $e )
67
		{
68
			throw new \Aimeos\MW\Media\Exception( $e->getMessage() );
69
		}
70
71
		$this->options = $options;
72
	}
73
74
75
	/**
76
	 * Cleans up
77
	 */
78
	public function __destruct()
79
	{
80
		if( $this->image ) {
81
			$this->image->clear();
82
		}
83
	}
84
85
86
	/**
87
	 * Clone resources
88
	 */
89
	public function __clone()
90
	{
91
		$this->image = clone $this->image;
92
	}
93
94
95
	/**
96
	 * Returns the height of the image
97
	 *
98
	 * @return int Height in pixel
99
	 */
100
	public function getHeight() : int
101
	{
102
		return $this->image->getImageHeight();
103
	}
104
105
106
	/**
107
	 * Returns the width of the image
108
	 *
109
	 * @return int Width in pixel
110
	 */
111
	public function getWidth() : int
112
	{
113
		return $this->image->getImageWidth();
114
	}
115
116
117
	/**
118
	 * Stores the media data at the given file name.
119
	 *
120
	 * @param string|null $filename File name to save the data into or null to return the data
121
	 * @param string|null $mimetype Mime type to save the content as or null to leave the mime type unchanged
122
	 * @return string|null File content if file name is null or null if data is saved to the given file name
123
	 * @throws \Aimeos\MW\Media\Exception If image couldn't be saved to the given file name
124
	 */
125
	public function save( string $filename = null, string $mimetype = null ) : ?string
126
	{
127
		if( empty( $mimetype ) ) {
128
			$mimetype = $this->getMimeType();
129
		}
130
131
		$quality = 90;
132
		$mime = explode( '/', $mimetype );
133
134
		if( isset( $this->options['image']['quality'] ) ) {
135
			$quality = max( min( (int) $this->options['image']['quality'], 100 ), 0 );
136
		}
137
138
		try
139
		{
140
			$this->image->setImageFormat( $mime[1] ?? 'jpeg' );
141
			$this->image->setImageCompressionQuality( $quality );
142
143
			if( $filename === null ) {
144
				return $this->image->getImagesBlob();
145
			}
146
147
			$this->image->writeImage( $filename );
148
		}
149
		catch( \Exception $e )
150
		{
151
			throw new \Aimeos\MW\Media\Exception( $e->getMessage() );
152
		}
153
154
		return null;
155
	}
156
157
158
	/**
159
	 * Scales the image to the given width and height.
160
	 *
161
	 * @param int|null $width New width of the image or null for automatic calculation
162
	 * @param int|null $height New height of the image or null for automatic calculation
163
	 * @param int $fit "0" keeps image ratio, "1" adds padding while "2" crops image to enforce image size
164
	 * @return \Aimeos\MW\Media\Image\Iface Self object for method chaining
165
	 */
166
	public function scale( int $width = null, int $height = null, int $fit = 0 ) : \Aimeos\MW\Media\Image\Iface
167
	{
168
		try
169
		{
170
			$newMedia = clone $this;
171
172
			if( $fit === 2 && $width && $height )
0 ignored issues
show
Bug Best Practice introduced by
The expression $height of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Bug Best Practice introduced by
The expression $width of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
173
			{
174
				$newMedia->image->cropThumbnailImage( (int) $width, (int) $height );
175
				// see https://www.php.net/manual/en/imagick.cropthumbnailimage.php#106710
176
				$newMedia->image->setImagePage( 0, 0, 0, 0 );
177
			}
178
			elseif( $fit === 1 && $width && $height )
0 ignored issues
show
Bug Best Practice introduced by
The expression $height of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Bug Best Practice introduced by
The expression $width of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
179
			{
180
				$w = ( $width - $newMedia->image->getImageWidth() ) / 2;
181
				$h = ( $height - $newMedia->image->getImageHeight() ) / 2;
182
183
				$newMedia->image->extentImage( $width, $height, (int) -$w, (int) -$h );
184
			}
185
			else
186
			{
187
				$w = $this->image->getImageWidth();
188
				$h = $this->image->getImageHeight();
189
190
				list( $newWidth, $newHeight ) = $this->getSizeFitted( $w, $h, $width, $height );
191
192
				if( $w > $newWidth || $h > $newHeight ) {
193
					$newMedia->image->resizeImage( $newWidth, $newHeight, \Imagick::FILTER_CUBIC, 0.8 );
194
				}
195
			}
196
197
			return $this->watermark( $newMedia );
198
		}
199
		catch( \Exception $e )
200
		{
201
			throw new \Aimeos\MW\Media\Exception( $e->getMessage() );
202
		}
203
	}
204
205
206
	/**
207
	 * Returns the supported image mime types
208
	 *
209
	 * @param array|string $mimetypes Mime type or list of mime types to check against
210
	 * @return array List of supported mime types
211
	 */
212
	public static function supports( $mimetypes = [] ) : array
213
	{
214
		$types = [
215
			'BMP' => 'image/bmp', 'GIF' => 'image/gif', 'JPEG' => 'image/jpeg',
216
			'PNG' => 'image/png', 'TIFF' => 'image/tiff', 'WEBP' => 'image/webp'
217
		];
218
		$list = [];
219
		$supported = \Imagick::queryFormats();
220
221
		foreach( $types as $key => $type )
222
		{
223
			if( in_array( $key, $supported ) ) {
224
				$list[] = $type;
225
			}
226
		}
227
228
		return empty( $mimetypes ) ? $list : array_intersect( $list, (array) $mimetypes );
229
	}
230
231
232
	/**
233
	 * Adds the configured water mark to the image
234
	 *
235
	 * @param \Aimeos\MW\Media\Image\Iface $media Media object the watermark should be applied to
236
	 * @return \Aimeos\MW\Media\Image\Iface Media object with watermark
237
	 */
238
	protected function watermark( \Aimeos\MW\Media\Image\Iface $media )
239
	{
240
		if( self::$wmimg === null ) {
241
			return $media;
242
		}
243
244
		$wh = self::$wmimg->getImageHeight();
245
		$ww = self::$wmimg->getImageWidth();
246
247
		if( $ww > $media->getWidth() )
248
		{
249
			$wh = $media->getWidth() / $ww * $wh;
250
			$ww = $media->getWidth();
251
		}
252
253
		if( $wh > $media->getHeight() )
254
		{
255
			$ww = $media->getHeight() / $wh * $ww;
256
			$wh = $media->getHeight();
257
		}
258
259
		$ww = round( $ww );
260
		$wh = round( $wh );
261
262
		$dx = round( ( $media->getWidth() - $ww ) / 2 );
263
		$dy = round( ( $media->getHeight() - $wh ) / 2 );
264
265
		if( !isset( self::$wmimages[$ww . 'x' . $wh] ) )
266
		{
267
			$wmimage = clone self::$wmimg;
268
			$wmimage->resizeImage( $ww, $wh, \Imagick::FILTER_CUBIC, 0.8 );
269
			self::$wmimages[$ww . 'x' . $wh] = $wmimage;
270
		}
271
		else
272
		{
273
			$wmimage = self::$wmimages[$ww . 'x' . $wh];
274
		}
275
276
		$wmimage->setImageColorspace( $media->image->getImageColorspace() );
0 ignored issues
show
Bug introduced by
Accessing image on the interface Aimeos\MW\Media\Image\Iface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
277
		$media->image->compositeImage( $wmimage, \Imagick::COMPOSITE_OVER, $dx, $dy );
278
279
		return $media;
280
	}
281
}
282