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

Standard::supports()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 6
nop 1
dl 0
loc 17
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2014
6
 * @copyright Aimeos (aimeos.org), 2015-2022
7
 * @package MW
8
 * @subpackage Media
9
 */
10
11
12
namespace Aimeos\MW\Media\Image;
13
14
15
/**
16
 * Default image class using GDLib.
17
 *
18
 * @package MW
19
 * @subpackage Media
20
 */
21
class Standard
22
	extends \Aimeos\MW\Media\Image\Base
23
	implements \Aimeos\MW\Media\Image\Iface
24
{
25
	private static $watermark;
26
27
	private $options;
28
	private $image;
29
	private $mimetype;
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
		if( !self::$watermark && isset( $options['image']['watermark'] ) )
45
		{
46
			if( ( $watermark = @file_get_contents( $options['image']['watermark'] ) ) === false )
47
			{
48
				$msg = sprintf( 'Watermark image "%1$s" not found', $options['image']['watermark'] );
49
				throw new \Aimeos\MW\Media\Exception( $msg );
50
			}
51
52
			if( ( $image = @imagecreatefromstring( $watermark ) ) === false ) {
53
				throw new \Aimeos\MW\Media\Exception( sprintf( 'The watermark image isn\'t supported by GDlib' ) );
54
			}
55
56
			self::$watermark = $image;
57
		}
58
59
		if( ( $this->image = @imagecreatefromstring( $content ) ) === false ) {
60
			throw new \Aimeos\MW\Media\Exception( sprintf( 'The image type isn\'t supported by GDlib' ) );
61
		}
62
63
		if( imagealphablending( $this->image, true ) === false ) {
64
			throw new \Aimeos\MW\Media\Exception( sprintf( 'GD library failed (imagealphablending)' ) );
65
		}
66
67
		$this->options = $options;
68
		$this->mimetype = $mimetype;
69
	}
70
71
72
	/**
73
	 * Cleans up
74
	 */
75
	public function __destruct()
76
	{
77
		if( $this->image ) {
78
			imagedestroy( $this->image );
79
		}
80
	}
81
82
83
	/**
84
	 * Returns the height of the image
85
	 *
86
	 * @return int Height in pixel
87
	 */
88
	public function getHeight() : int
89
	{
90
		return imagesy( $this->image );
91
	}
92
93
94
	/**
95
	 * Returns the width of the image
96
	 *
97
	 * @return int Width in pixel
98
	 */
99
	public function getWidth() : int
100
	{
101
		return imagesx( $this->image );
102
	}
103
104
105
	/**
106
	 * Stores the media data at the given file name.
107
	 *
108
	 * @param string|null $filename File name to save the data into or null to return the data
109
	 * @param string|null $mimetype Mime type to save the content as or null to leave the mime type unchanged
110
	 * @return string|null File content if file name is null or null if data is saved to the given file name
111
	 * @throws \Aimeos\MW\Media\Exception If image couldn't be saved to the given file name
112
	 */
113
	public function save( string $filename = null, string $mimetype = null ) : ?string
114
	{
115
		$result = null;
116
117
		if( $mimetype === null ) {
118
			$mimetype = $this->getMimeType();
119
		}
120
121
		if( self::$watermark !== null ) {
122
			$this->watermark();
123
		}
124
125
		$quality = 90;
126
		if( isset( $this->options['image']['quality'] ) ) {
127
			$quality = max( min( (int) $this->options['image']['quality'], 100 ), 0 );
128
		}
129
130
		if( $filename === null ) {
131
			ob_start();
132
		}
133
134
		try
135
		{
136
			switch( $mimetype )
137
			{
138
				case 'image/gif':
139
140
					if( @imagegif( $this->image, $filename ) === false ) {
141
						throw new \Aimeos\MW\Media\Exception( sprintf( 'Unable to save image to file "%1$s"', $filename ) );
142
					}
143
144
					break;
145
146
				case 'image/jpeg':
147
148
					if( @imagejpeg( $this->image, $filename, $quality ) === false ) {
149
						throw new \Aimeos\MW\Media\Exception( sprintf( 'Unable to save image to file "%1$s"', $filename ) );
150
					}
151
152
					break;
153
154
				case 'image/webp':
155
156
					if( @imagewebp( $this->image, $filename, $quality ) === false ) {
157
						throw new \Aimeos\MW\Media\Exception( sprintf( 'Unable to save image to file "%1$s"', $filename ) );
158
					}
159
160
					break;
161
162
				case 'image/png':
163
164
					if( imagesavealpha( $this->image, true ) === false ) {
165
						throw new \Aimeos\MW\Media\Exception( sprintf( 'GD library failed (imagesavealpha)' ) );
166
					}
167
168
					if( @imagepng( $this->image, $filename, (int) 10 - $quality / 10 ) === false ) {
169
						throw new \Aimeos\MW\Media\Exception( sprintf( 'Unable to save image to file "%1$s"', $filename ) );
170
					}
171
172
					break;
173
174
				default:
175
					throw new \Aimeos\MW\Media\Exception( sprintf( 'File format "%1$s" is not supported', $mimetype ) );
176
			}
177
		}
178
		finally
179
		{
180
			if( $filename === null ) {
181
				$result = ob_get_clean();
182
			}
183
		}
184
185
		return $result;
186
	}
187
188
189
	/**
190
	 * Scales the image to the given width and height.
191
	 *
192
	 * @param int|null $width New width of the image or null for automatic calculation
193
	 * @param int|null $height New height of the image or null for automatic calculation
194
	 * @param int $fit "0" keeps image ratio, "1" adds padding while "2" crops image to enforce image size
195
	 * @return \Aimeos\MW\Media\Image\Iface Self object for method chaining
196
	 */
197
	public function scale( int $width = null, int $height = null, int $fit = 0 ) : Iface
198
	{
199
		$w = imagesx( $this->image );
200
		$h = imagesy( $this->image );
201
202
		if( $fit === 2 && $width && $height )
0 ignored issues
show
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...
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...
203
		{
204
			$ratio = ( $w < $h ? $width / $w : $height / $h );
205
			$newHeight = (int) $h * $ratio;
206
			$newWidth = (int) $w * $ratio;
207
		}
208
		else
209
		{
210
			list( $newWidth, $newHeight ) = $this->getSizeFitted( $w, $h, $width, $height );
211
212
			if( !$fit && $w <= $newWidth && $h <= $newHeight ) {
213
				return $this;
214
			}
215
		}
216
217
		return $this->resize( $newWidth, $newHeight, $width, $height, !$fit );
218
	}
219
220
221
	/**
222
	 * Returns the supported image mime types
223
	 *
224
	 * @param array|string $mimetypes Mime type or list of mime types to check against
225
	 * @return array List of supported mime types
226
	 */
227
	public static function supports( $mimetypes = [] ) : array
228
	{
229
		$types = [
230
			IMG_BMP => 'image/bmp', IMG_GIF => 'image/gif', IMG_JPG => 'image/jpeg', IMG_PNG => 'image/png',
231
			IMG_WBMP => 'image/wbmp', IMG_XPM => 'image/xpm', IMG_WEBP => 'image/webp'
232
		];
233
		$list = [];
234
		$supported = imagetypes();
235
236
		foreach( $types as $key => $type )
237
		{
238
			if( $key & $supported ) {
239
				$list[] = $type;
240
			}
241
		}
242
243
		return empty( $mimetypes ) ? $list : array_intersect( $list, (array) $mimetypes );
244
	}
245
246
247
	/**
248
	 * Resizes and crops the image if necessary
249
	 *
250
	 * @param int $scaleWidth Width of the image before cropping
251
	 * @param int $scaleHeight Height of the image before cropping
252
	 * @param int|null $width New width of the image
253
	 * @param int|null $height New height of the image
254
	 * @param bool $fit True to keep the width/height ratio of the image
255
	 * @return \Aimeos\MW\Media\Image\Iface Resized media object
256
	 */
257
	protected function resize( int $scaleWidth, int $scaleHeight, int $width = null, int $height = null, bool $fit = true ) : Iface
258
	{
259
		if( !( $width || $height ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $height of type integer|null is loosely compared to false; 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 false; 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...
260
			return $this;
261
		}
262
263
		if( ( $result = imagescale( $this->image, $scaleWidth, $scaleHeight ) ) === false ) {
264
			throw new \Aimeos\MW\Media\Exception( 'Unable to scale image' );
265
		}
266
267
		$newMedia = clone $this;
268
269
		$width = ( $width ?: $scaleWidth );
270
		$height = ( $height ?: $scaleHeight );
271
272
		$x0 = (int) ( $width / 2 - $scaleWidth / 2 );
273
		$y0 = (int) ( $height / 2 - $scaleHeight / 2 );
274
275
		if( $fit === false && ( $x0 || $y0 ) )
276
		{
277
			if( ( $newImage = imagecreatetruecolor( $width, $height ) ) === false ) {
278
				throw new \Aimeos\MW\Media\Exception( 'Unable to create new image' );
279
			}
280
281
			imagesavealpha( $newImage, true );
282
			$alpha = in_array( $this->mimetype, ['image/gif', 'image/png'] ) ? 127 : 0;
283
284
			if( ( $bg = imagecolorallocatealpha( $newImage, 255, 255, 255, $alpha ) ) === false ) {
285
				throw new \Aimeos\MW\Media\Exception( 'Unable to allocate color' );
286
			}
287
288
			if( imagefill( $newImage, 0, 0, $bg ) === false ) {
289
				throw new \Aimeos\MW\Media\Exception( 'Unable to fill background' );
290
			}
291
292
			if( imagecopy( $newImage, $result, $x0, $y0, 0, 0, $scaleWidth, $scaleHeight ) === false ) {
293
				throw new \Aimeos\MW\Media\Exception( 'Unable to crop image' );
294
			}
295
296
			imagedestroy( $result );
297
			$newMedia->image = $newImage;
298
		}
299
		else
300
		{
301
			$newMedia->image = $result;
302
		}
303
304
		return $newMedia;
305
	}
306
307
308
	/**
309
	 * Adds the configured water mark to the image
310
	 */
311
	protected function watermark()
312
	{
313
		$ww = imagesx( self::$watermark );
314
		$wh = imagesy( self::$watermark );
315
316
		$ratio = min( $this->getWidth() / $ww, $this->getHeight() / $wh );
317
		$newHeight = (int) ( $wh * $ratio );
318
		$newWidth = (int) ( $ww * $ratio );
319
320
		if( ( $wimage = imagescale( self::$watermark, $newWidth, $newHeight ) ) === false ) {
321
			throw new \Aimeos\MW\Media\Exception( 'Unable to scale image' );
322
		}
323
324
		$dx = (int) ( $this->getWidth() - $newWidth ) / 2;
325
		$dy = (int) ( $this->getHeight() - $newHeight ) / 2;
326
327
		if( imagecopy( $this->image, $wimage, $dx, $dy, 0, 0, $newWidth, $newHeight ) === false ) {
328
			throw new \Aimeos\MW\Media\Exception( 'Failed to apply watermark immage' );
329
		}
330
331
		imagedestroy( $wimage );
332
	}
333
}
334